2020CCPC长春站题解A D F H J K
都过了一周了,才来补题,不愧是我,摸鱼的神。
题意:充n块钱,不同档位有首冲奖励,问最多能得游戏币。
思路:充n块钱,能得n*10的游戏币(一开始没注意,+1告诉我才发现),然后首冲奖励每个档位只有有一次就是01背包了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=2e3+11; 5 const int a[11]={ 6 1,6,28,88,198,328,648, 7 }; 8 const int b[11]={ 9 8,18,28,58,128,198,388, 10 }; 11 int dp[N]; 12 int main(){ 13 int n; 14 scanf("%d",&n); 15 for(int i=0;i<7;++i){ 16 for(int j=n;j>=a[i];--j) dp[j]=max(dp[j],dp[j-a[i]]+b[i]); 17 } 18 printf("%d\n",10*n+dp[n]); 19 return 0; 20 }
题意:
思路:一开始看出an&i打表打错了,然后按照an&i进行打表,就可以看出比如c是3时
n: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
an:1 3 3 9 3 9 9 27 3 9 9 27 9 27 27 81 3
可以观察到,从2^i开始到2^(i+1)-1的数是0到2^i-1的数的c倍(又是+1告诉我才发现的,我真是粗心鬼)
当时赛场上是让+1敲了,我自己做一遍的话思路与赛场上一样,依旧是一个类似快速幂的思想。
我们可以先预处理出到每一位i时的前n项和,从高位到低位,有1的位就统计答案,同时将下一位的权重增加c倍。
因为是从0开始,然后最高位有个3,那么我们可以先把这个3拿出来,把后面的位的答案加上先,最后再把这个3加回去(此时权重变化,可能就不再是3了)。
比如n=1101,c=3,那么这时就是求1 3 3 9 3 9 9 27 3 9 9 27 9 27这一段的和
单独看每个1的话,第一个1自然是1 3 3 9 3 9 9 27 3这一段,第二个1是1 3 3 9,第三个1是1 3这一段,
然后我们把第一个1的3先去掉,也就是变成了1 3 3 9 3 9 9 27后面接上3*(1 3 3 9)再接上9*(1 3),最后把27加上。
//可以打表观察到,从2^i开始到2^(i+1)-1的数是0到2^i-1的数的c倍 //那么我们可以先预处理出到每一位i时的前n项和,然后有1的地方便统计答案 //每统计一次,这个i长度的前缀和就乘c #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=3e3+11,md=1e9+7; char s[N]; ll a[N]; int main(){ int n,c; a[0]=1; printf("1 "); for(int i=1;i<=16;++i){ ll mx=0; for(int j=0;j<i;++j) mx=max(mx,a[i&j]); a[i]=mx*3; printf("%lld ",a[i]); } scanf("%s%d",s,&c); n=strlen(s); for(int i=1;i<n;++i) a[i]=a[i-1]*(c+1)%md; ll pw=1,ans=0; for(int i=0;i<n;++i){ if(s[i]=='1'){ ans=(ans+pw*a[n-i-1]%md)%md; pw=pw*c%md; } } ans=(ans+pw)%md; printf("%lld\n",ans); return 0; }
题意:树上的点有权值ai,求
思路:树启发模板题,第二次做树启题,有点不自信,跟学弟口胡半天没敢写,最后按照一开始的思路,直接过了,意料之外的没有超时。
对于异或结果相同的位置对,我们只需要去拆位,然后统计下每个数相应的每一位有多少个0和1即可。需要注意异或结果超1e6,用来保存结果的桶得开大点。
核心思路便在于,对于当前点u,假设我们已经统计下了某个儿子v1上的权值情况,那么我们可以再去枚举v2上的点,就可以在v1中去找相应的a[u]^a[v2]计算对答案的贡献。
那么便根据重链和轻链的思想,对于当前节点,我们已经保存了重儿子的信息,那么接下来只需要再去枚举轻儿子即可。但这样的话只算了轻对重的贡献,还有轻对轻的贡献,也要考虑。我的处理是,当前节点跑完某个轻儿子后,再跑一遍这个轻儿子,把它的信息保存下来,算完当前节点子树对答案的贡献之后,再又把它轻儿子的信息去掉。
//树上启发式,多跑一遍算答案 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+11,M=2e6+11; vector<int> vv[N]; ll ans=0; int a[N],size[N],son[N],cnt[M][21][2]; void dfs1(int u,int f){ size[u]=1; int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f) continue; dfs1(v,u); size[u]+=size[v]; if(size[v]>size[son[u]]) son[u]=v; } return ; } void add(int x,int val){ for(int i=0;i<20;++i){ if(x&(1<<i)) cnt[a[x]][i][1]+=val; else cnt[a[x]][i][0]+=val; } return ; } void ac(int x,int y){ for(int i=0;i<20;++i){ if(y&(1<<i)) ans+=(1ll<<i)*cnt[x][i][0]; else ans+=(1ll<<i)*cnt[x][i][1]; } return ; } void dot2(int u,int f,int tf,int val){ add(u,val); int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[tf]) continue; dot2(v,u,tf,val); } } void dot1(int u,int f,int tf){ ac(a[u]^a[tf],u); int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[tf]) continue; dot1(v,u,tf);//统计答案 if(u==tf) dot2(v,u,tf,1);//加上轻儿子信息 } if(u==tf){ for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[tf]) continue; dot2(v,u,tf,-1);//把轻儿子信息去掉 } } } void dfs2(int u,int f,int op){ int lenv=vv[u].size(); for(int i=0;i<lenv;++i){ int v=vv[u][i]; if(v==f||v==son[u]) continue; dfs2(v,u,0); } if(son[u]) dfs2(son[u],u,1); //多加个函数计算答案 dot1(u,f,u); //正常树上启发式过程 dot2(u,f,u,1); if(!op) dot2(u,f,0,-1); } int main(){ int n,u,v; scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&a[i]); for(int i=1;i<n;++i){ scanf("%d%d",&u,&v); vv[u].push_back(v); vv[v].push_back(u); } ans=0; dfs1(1,0); dfs2(1,0,1); printf("%lld\n",ans); return 0; }
题意:一个锁,有n位数,每位数取值0~9,每次操作可以把某一位+1或者-1%10,有些状态不能到达。不能操作就输,问先手能不能赢。
思路:当前+1和学弟这题初步的思路挂了,又没有什么队出这题的时候,我还以为这题很难,就没有深入的想。但这题知道思想后,真不难,我真是个小垃圾,那时没有继续的跟队友进行深入的讨论。
之前做过个一般图博弈,而这个二分图博弈也类似,胜败就是看还有没有增广路。
每个状态的数位和要么是奇数要么是偶数,而每次操作便是改变了奇偶。那么状态可以分成X集合和Y集合,X集合一次操作能到达Y集合某个状态,便是它们之间存在边。
那么先手能不能赢,便看加入初状态后,存不存在一条新的增广路。用最大流来写的话,就是看加入初状态,能不能使得最大流量增加。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+11,M=N*71,inf=0x3f3f3f3f; struct Side{ int v,ne,w; }S[M]; int ban[N],iso[N]; int n,sn,sb,se,head[N],dep[N],cur[N]; void inito(){ for(int i=0;i<N;++i){ int temp=i,sum=0; while(temp){ sum+=temp%10; temp/=10; } iso[i]=sum&1; } } void init(int n){ sn=0; sb=n-1;se=n; for(int i=0;i<=n;i++){ ban[i]=0; head[i]=-1; } } void addE(int u,int v,int w){ S[sn].w=w;S[sn].v=v; S[sn].ne=head[u]; head[u]=sn++; } void addS(int u,int v,int w){ addE(u,v,w);addE(v,u,0); } bool bfs(int n){ queue<int> q; for(int i=0;i<=n;i++) dep[i]=0; dep[sb]=1; q.push(sb); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];~i;i=S[i].ne){ int v=S[i].v; cur[v]=head[v]; if(!dep[v]&&S[i].w>0){ dep[v]=dep[u]+1; q.push(v); if(v==se) return 1; } } } return 0; } int dfs(int u,int minf){ if(u==se||!minf) return minf; for(int &i=cur[u];~i;i=S[i].ne){ int v=S[i].v; if(S[i].w>0&&dep[v]==dep[u]+1){ int flow=dfs(v,min(minf,S[i].w)); if(flow>0){ S[i].w-=flow; S[i^1].w+=flow; return flow; } } } return 0; } int dinic(int n){ int ans=0,flow=0; while(bfs(n)){ while(flow=dfs(sb,inf)) ans+=flow; } return ans; } int main(){ inito(); int t; scanf("%d",&t); while(t--){ int n,m,beg,x,lim=1; scanf("%d%d%d",&n,&m,&beg); for(int i=1;i<=n;++i) lim*=10; init(lim+2); while(m--){ scanf("%d",&x); ban[x]=1; } for(int i=0;i<lim;++i){ if(ban[i]) continue; if(iso[i]){ if(i!=beg) addS(sb,i,1); for(int j=1;j<lim;j*=10){ int pw=i/j%10,v1,v2; if(pw==9) v1=i-9*j; else v1=i+j; if(pw==0) v2=i+9*j; else v2=i-j; if(!ban[v1]) addS(i,v1,1); if(!ban[v2]) addS(i,v2,1); } }else if(i!=beg) addS(i,se,1); } dinic(lim+2); if(iso[beg]) addS(sb,beg,1); else addS(beg,se,1); if(dinic(lim+2)) printf("Alice\n"); else printf("Bob\n"); } return 0; }
题意:已经存在一些有限制的圆,再往上加同样有限制的圆,问有多少种方案。
思路:思路来源
因为半径只有5,那么每个点能不能在某个圆上只与它周围10个点有关
设dp[i][j]就是到i点,然后i,i-1,i-2...i-9的状态为j的方法数,对于状态j有10位0,1,2,3,4,5,6,7,8,9,第k位为1便说明这个地方被圆包含了
然后右边界的在i上的圆的左边界就不能在i-1-k这个位置,所以就枚举右边界在i+1上的状态向i+1进行转移即可
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e3+11,M=(1<<10)+11; const int md=1e9+7,sta1=(1<<10)-1,sta2=(1<<5)-1; int dp[N][M],zt1[N],zt2[N]; vector<int> limr[N]; //zt1保存该状态的左边界在哪,用来判断能不能向下一个位置转移 //zt2保存该状态覆盖了那些点,使得这些点不能作为右边界在下一个位置的圆的左边界 void init(){ for(int i=0;i<=sta2;++i){ for(int j=0;j<10;j+=2){ if(i&(1<<(j>>1))){ zt1[i]|=1<<(j+1); zt2[i]=max(zt2[i],(1<<(j+1))-1); } } } } int main(){ init(); int n,k,x,r; scanf("%d%d",&n,&k); for(int i=1;i<=k;++i){ scanf("%d%d",&x,&r); limr[x+r].push_back((r-1)<<1); } dp[0][sta1]=1; for(int i=0;i<n;++i){ //f1同zt1,ff1同zt2 int lenv=limr[i+1].size(),f1=0,ff1=0; for(int j=0;j<lenv;++j){ f1|=1<<(limr[i+1][j]+1); ff1=max(ff1,(1<<(limr[i+1][j]+1))-1); } for(int j=0;j<=sta1;++j){ if(!dp[i][j]) continue; int f2=(j<<1)&sta1; if(f1&f2) continue; for(int k=0;k<=sta2;++k){ if(f1&zt1[k]) continue; if(f2&zt1[k]) continue; dp[i+1][ff1|f2|zt2[k]]+=dp[i][j]; dp[i+1][ff1|f2|zt2[k]]%=md; } } } int ans=0; for(int i=0;i<=sta1;++i) ans=(ans+dp[n][i])%md; printf("%d\n",ans); return 0; }
题意:一开始n个带有权值a[i]的点,m次操作。op 1:添加一个a[x]=y的点x,op 2:把x点和y点所在的树合并,op 3:修改点x的权值为y。每次操作后,问在有多少在同一树上的点对满足gcd(a[x],a[y])=a[x]^a[y]。
思路:对于gcd(x,y)=x^y。假设g=gcd(x,y),x%g=0,那么我们可以枚举x的约数g,然后就有y=x^g,此时通过判断gcd(x,y)是否等于g,就可以预处理得到与x满足条件的所有y,而已可以得出对于每个x与之对于的y不超过60个。那么接下来的操作1,2,3就是并查集的按秩合并了,也就是启发式的思想。
其中预处理可以优化掉一个求gcd的log,。
#include<bits/stdc++.h> #include<tr1/unordered_map> using namespace std; typedef long long ll; const int N=3e5+11,M=2e5+11; vector<int> vv[M]; int a[N],fa[N],size[N]; tr1::unordered_map<int,int> mmp[N]; tr1::unordered_map<int,int>::iterator it; void init(){ for(int i=1;i<M;++i){ for(int j=i<<1;j<M;j+=i){ int k=(i^j); if(__gcd(j,k)==i) vv[j].push_back(k); } } } void init2(){ for(int i=1;i<M;++i){ for(int j=i<<1;j<M;j+=i){ if(i+j<M&&(i^j)==i+j){ vv[j].push_back(i+j); vv[i+j].push_back(j); } } } } int find(int x){ return fa[x]==x ? x : fa[x]=find(fa[x]); } int main(){ init2(); int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ fa[i]=i;size[i]=1; scanf("%d",&a[i]); ++mmp[i][a[i]]; } ll ans=0; int op,x,y,fx,fy; while(m--){ scanf("%d%d%d",&op,&x,&y); if(op==1){ a[x]=y; fa[x]=x; size[x]=1; ++mmp[x][y]; }else if(op==2){ fx=find(x),fy=find(y); if(fx!=fy){ if(size[fx]>size[fy]) swap(fx,fy); for(it=mmp[fx].begin();it!=mmp[fx].end();++it){ x=it->first;y=it->second; for(int i=0;i<(int)vv[x].size();++i){ if(mmp[fy].count(vv[x][i])) ans+=1ll*y*mmp[fy][vv[x][i]]; } } fa[fx]=fy; size[fy]+=size[fx]; for(it=mmp[fx].begin();it!=mmp[fx].end();++it){ x=it->first;y=it->second; mmp[fy][x]+=y; } size[fx]=0; mmp[fx].clear(); } }else{ fx=find(x); for(int i=0;i<(int)vv[a[x]].size();++i){ if(mmp[fx].count(vv[a[x]][i])) ans-=mmp[fx][vv[a[x]][i]]; } --mmp[fx][a[x]]; for(int i=0;i<(int)vv[y].size();++i){ if(mmp[fx].count(vv[y][i])) ans+=mmp[fx][vv[y][i]]; } a[x]=y; ++mmp[fx][y]; } printf("%lld\n",ans); } return 0; }