周赛77
B
注意力
注意到我们要求每个长度为9的子数组都出现1-9,那么实际上构造方式只能是选择一个1-9的排列然后一直循环,最后剩下的不到9个也可以利用前面的组成长度为9的排列。
但是注意到这样是不行的,也就是只有剩余不到9个的数里没有重复的才行。这个条件也可以转化为,所有数字中最大出现次数-最小出现次数不能超过1
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
int cnt[10]={0};
for(int i=0;i<n;i++){
int t;
cin>>t;
cnt[t]++;
}
int mn=1e9,mx=0;
for(int i=1;i<=9;i++){
mn=min(mn,cnt[i]);
mx=max(mx,cnt[i]);
}
if(mx-mn<=1){
cout<<"YES\n";
}
else{
cout<<"NO\n";
}
}
C
数论
给你两个数ab,问他们的线性组合能不能等于某个特定的数k?实际上他们的线性组合,最后能得到的数一定是
的倍数,所以我们看k是不是
的倍数就行了,这是裴蜀定理的一个重要结论
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll gcd(ll x,ll y){
return y?gcd(y,x%y):x;
}
int main(){
int x,y,a,b,c,d;
int t;
cin>>t;
while(t--){
cin>>x>>y>>a>>b>>c>>d;
int g1=gcd(a,b);
int g2=gcd(c,d);
if(y%g1==0&&x%g2==0){
cout<<"YES\n";
}
else{
cout<<"NO\n";
}
}
}
D
位运算+并查集
两个数字按位与不为0就有连边,问最后最大的连通块的大小?
按位与不为0的意思就是存在某一位,两个数在这一位都为1。那么我们可以把元素放进位对应地桶里,然后对于每个数字,把它为1的位都连接起来,合并对应的桶,最后去重一下看最大集合就行
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin>>t;
while(t--){
set<int>s[70];
int n;
cin>>n;
int vis[70][70]={0};
for(int k=0;k<n;k++){
long long x;
cin>>x;
for(int i=0;i<=60;i++){
if(x>>i&1){
s[i].insert(k);
for(int j=i+1;j<=60;j++){
if(x>>j&1){
vis[i][j]=1;
}
}
}
}
}
int f[70];
for(int i=0;i<=60;i++)f[i]=i;
auto &&find=[&](auto &&find,int x)->int{
if(f[x]==x)return x;
return find(find,f[x]);
};
for(int i=0;i<=60;i++){
for(int j=i+1;j<=60;j++){
if(vis[i][j]){
int fx=find(find,i),fy=find(find,j);
if(fx!=fy){
f[fx]=fy;
for(int x:s[fx]){
s[fy].insert(x);
}
}
}
}
}
int ans=0;
for(int i=0;i<=60;i++){
ans=max(ans,(int)s[i].size());
}
cout<<ans<<'\n';
}
}
E
巧妙的前缀和+维护出现下标
给一个01串,问一个区间里所有子数组的按位或之和。实际上就是问区间内至少含一个1的的子数组的数量。正难则反,可以算区间内全零子数组的数量。然后这个东西可以前缀和维护。但问题是可能可能正好在某个0区间的内部,答案并不完整,所以我们需要找到
内部一个完整的地方,也就是a[l]=a[r]=1的
,这个区间内的答案都是完整的,具体来说我们可以维护一个位置两侧的第一个1的位置,然后l1就是l后面第一个1的位置,r1就是r前面第一个1的位置。然后我们还要处理
这两段的答案,这两段其实都是全0,和前面一样的处理
#include<bits/stdc++.h>
using namespace std;
#define int long long
int sum[202020],pre[202020],nxt[202020];
int cal(int x){
return x*(x+1)/2;
}
signed main(){
int n;
cin>>n;
string s;
cin>>s;
s=' '+s;
int len=0;
for(int i=1;i<=n;i++){
if(s[i]=='0'){
len++;
sum[i]=sum[i-1];
}
else{
sum[i]=sum[i-1]+cal(len);
len=0;
}
}
for(int i=1;i<=n;i++){
if(s[i]=='1'){
pre[i]=i;
}
else{
pre[i]=pre[i-1];
}
}
nxt[n+1]=n+1;
for(int i=n;i>=1;i--){
if(s[i]=='1'){
nxt[i]=i;
}
else{
nxt[i]=nxt[i+1];
}
}
int q;
cin>>q;
while(q--){
int l,r;
cin>>l>>r;
int l1=nxt[l];
int r1=pre[r];
int ans=cal(r-l+1);
if(l1>r1){
ans=0;
}
else{
ans-=sum[r1]-sum[l1];
ans-=cal(r-r1);
ans-=cal(l1-l);
}
cout<<ans<<'\n';
}
}
F
树形dp
一棵树上选出一些点,从这个集合里随机选两个点uv,可以相同,问所有选法中,每个点作为出现了多少次。
一个点i能作为uv的lca,其实就是要么u在i的一个子树,v在另一个子树,要么是u就是i,v在一个子树里,要么是u=v=i。这三种情况分开讨论就行了
第一种情况,我们可以统计每个点为根的子树中,集合中的点的个数,然后对于一个子树i,对答案的贡献就是,其中
是所有子树的
之和,虽然uv可以交换位置,应该乘二,但是这个计算方式已经计算了交换位置的情况了,不用乘
第二种情况,只有在当前节点i在集合里时才有贡献,贡献就是,含义同上,这里没有考虑交换位置的情况,但是也可以交换,所以应该乘二
第三种情况,只有在i在集合里才有贡献,贡献就是1,因为,交换了也是一样的,这个组合只会出现一次
#include<bits/stdc++.h>
using namespace std;
vector<int>a[101010];
long long cnt[101010],sz[101010],vis[101010];
void dfs(int u,int f){
for(int v:a[u]){
if(v==f)continue;
dfs(v,u);
sz[u]+=sz[v];
}
long long sum=0,tot=0;
for(int v:a[u]){
if(v==f)continue;
tot+=sz[v];
}
for(int v:a[u]){
if(v==f)continue;
sum+=(tot-sz[v])*sz[v];
}
cnt[u]+=sum;
if(vis[u])cnt[u]+=tot*2;
}
int main(){
int n;
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
a[u].push_back(v);
a[v].push_back(u);
}
int k;
cin>>k;
for(int i=0;i<k;i++){
int t;
cin>>t;
sz[t]=1;
cnt[t]=1;
vis[t]=1;
}
dfs(1,0);
for(int i=1;i<=n;i++)cout<<cnt[i]<<' ';
}