[题解] Codeforces Global Round 23 1746 A B C D E1 F 题解
求点赞
A. Maxmina
首先序列全0的情况肯定是NO。否则,如果,则在序列中随便找一个1,把他左边和右边分别用第一种操作不断缩,直到序列长度为k为止,最后用一次2操作变成一个1;如果,直接不断用2操作把序列缩成一个元素即可。所以最后的结论就是只要序列中有1就是YES,否则是NO。
时间复杂度。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <int,int> #define fi first #define se second #define mpr make_pair #define pb push_back using namespace std; int t,n,k,a[60]; int main() { cin>>t; rep(tn,t) { cin>>n>>k; bool ok=false; rep(i,n) { cin>>a[i]; ok|=(a[i]==1); } puts(ok ? "YES":"NO"); } return 0; }
B. Rebellion
我们把的操作称为对i的操作。我们是不会先,然后再对操作的,因为这样不如直接把加到最后加到的那个位置去。所以我们操作的对象只能是初始序列中,没被任何东西加过的元素。发现对于一个0的操作相当于是把他删除。如果还要对一些1操作,那肯定是操作原序列中最靠前的一些1比较好。假设我们对k个1进行操作,这些1应该去优先填补第i+1个1之后的那些0。如果填补不完,则必须对剩下的0操作,把它们删掉。枚举k,用前缀和计算即可。
时间复杂度。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <int,int> #define fi first #define se second #define mpr make_pair #define pb push_back using namespace std; int t,n,a[100010],bk0[100010],nxt[100010]; int main() { cin>>t; rep(tn,t) { cin>>n; rep(i,n) scanf("%d",&a[i]); int lst=-1,cc=0; for(int i=n-1;i>=0;--i) { if(a[i]==0) ++cc; else { nxt[i]=lst;lst=i; bk0[i]=cc; } } if(lst==-1) { puts("0"); continue; } int ans=bk0[lst],c1=0; rep(i,n) if(a[i]==1) { ++c1; int c0; if(nxt[i]==-1) ans=min(ans,c1); else { ans=min(ans,c1+max(0,bk0[nxt[i]]-c1)); } } printf("%d\n",ans); } return 0; }
C. Permutation Operations
这种最优化+构造方案的题,日常先猜个结论:可以把逆序对数变成0。其实确实可以:逆序对数是0的充要条件是,每个数前面都没有比他大的数;后缀+1的操作,我们给以n开头的后缀;后缀+2的操作,我们给以n-1开头的后缀后缀+n的操作,我们给以1开头的后缀。这样可以保证每个数前面都没有比他大的数。以上"以x开头的后缀"中的x均指序列中的初始值。
时间复杂度。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <int,int> #define fi first #define se second #define mpr make_pair #define pb push_back using namespace std; int t,n,a[100010]; vector <pii> v; int main() { cin>>t; rep(tn,t) { cin>>n; v.clear(); repn(i,n) scanf("%d",&a[i]),v.pb(mpr(a[i],i)); sort(v.begin(),v.end());reverse(v.begin(),v.end()); rep(i,v.size()) printf("%d ",v[i].se);puts(""); } return 0; }
D. Paths on the Tree
首先每条路径的终点一定是叶子,因为多往下延伸一点总能多点贡献。令表示以i为根的子树内,一共有j条路径的最大贡献。按照题目中的要求,如果一共有s个儿子,每个儿子都会分到或者条路径,且取到每种值的儿子数量是固定的,假设有个儿子取到条。以记忆化搜索的方式进行dp转移,转移的时候对所有儿子的排序,取这个值最大的cnt个儿子取条路径即可。手算一下会发现,对于任意节点i,会被搜索到的只有最多2种,也就是j的合法取值只有最多2种。我一开始用map存j居然T了,所以最好先把每个i的可能的j取值先预处理出来。
时间复杂度。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <LL,LL> #define fi first #define se second #define mpr make_pair #define pb push_back using namespace std; LL t,n,k,c[200010],bas[200010],dp[200010][2]; vector <LL> g[200010]; bool cmp(pii aa,pii bb){return aa.se-aa.fi>bb.se-bb.fi;} void dfsPre(LL pos,LL cc) { bas[pos]=cc; if(g[pos].size()==0) return; cc/=g[pos].size(); if(cc==0) return; rep(i,g[pos].size()) dfsPre(g[pos][i],cc); } LL dfs(LL pos,LL cc) { if(cc==0) return 0; LL w=(cc==bas[pos] ? 0:1); if(dp[pos][w]>-1) return dp[pos][w];//cout<<pos<<' '<<cc<<' '<<bas[pos]<<endl; LL ret=cc*c[pos]; if(g[pos].size()==0) return dp[pos][w]=ret; LL soncnt=g[pos].size(),bass=cc/soncnt,mo=bass+1,mocnt=cc%soncnt; vector <pii> vs; rep(i,g[pos].size()) { LL v1=dfs(g[pos][i],bass),v2=dfs(g[pos][i],mo); vs.pb(mpr(v1,v2)); } sort(vs.begin(),vs.end(),cmp); rep(i,vs.size()) if(i<mocnt) ret+=vs[i].se;else ret+=vs[i].fi; //if(pos==3&&cc==2) cout<<ret<<endl; return dp[pos][w]=ret; } int main() { cin>>t; rep(tn,t) { scanf("%lld%lld",&n,&k); rep(i,n+3) g[i].clear(); LL x; for(int i=2;i<=n;++i) { scanf("%lld",&x); g[x].pb(i); } repn(i,n) scanf("%lld",&c[i]); rep(i,n+3) rep(j,2) dp[i][j]=-1; dfsPre(1,k); LL ans=dfs(1,k); printf("%lld\n",ans); } return 0; }
E1. Joking (Easy Version)
我觉得这题挺难的,为啥小学生都会做啊为避免混淆,首先明确定义:一个询问的返回结果有两种:YES(Y),NO(N);一个询问的正确性有两种,真(T) 和 假(F)。
令当前n的可能值的集合为s。当时,把s尽量均匀地分成4份,。使用两次询问,分别问和。得到返回结果后,讨论这两个询问的正确性是TT、TF还是FT。比如当返回结果为YN时,正确性为TT则目标在s2内;正确性为TF则目标在s1内;正确性为FT则目标在s4内,所以应该淘汰s3。其他三种情况推理类似,具体来说:返回结果为YY淘汰s4,为YN淘汰s3,为NY淘汰s2,为NN淘汰s1。所以每2个这样的询问可以让s的大小大约乘。用下面的代码发现最多需要38次才能把s的大小减少到3以内,也就是76次操作。
int n=100000,cc=0; while(n>3) { n-=n/4; cc++; } cout<<cc;
如果现在s的大小<3的话,直接猜2次就行了。否则令s中的3个元素为,连续问以下四个问题:。我们的目标是淘汰1,2,3中的任意一个数,这样接下来可以猜两次达到目标。先看中间两个询问的返回结果,如果相同,则这两个正确性必须都是T,这就至少淘汰了一个数了;否则中间两个询问正确性必是一真一假,这时再看边上两个询问,如果返回结果相同,则也必须都是T,成功淘汰至少一个数;剩下的情况就是正确性为YNYN或NYNY了(两种对称的),用和上面类似的方法分类讨论一下,发现答案不可能是2,成功淘汰一个。总操作次数似乎最多刚好82次。
我一开始以为easy version是不用可以猜两次的特殊条件的,结果发现连n=2都做不了
时间复杂度,反正不超过。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <int,int> #define fi first #define se second #define mpr make_pair #define pb push_back using namespace std; int n; vector <int> v; vector <int> comb(vector <int> aa,vector <int> bb) { rep(i,bb.size()) aa.pb(bb[i]); return aa; } int ask(vector <int> aa) { cout<<"? "<<aa.size()<<' '; rep(i,aa.size()) cout<<aa[i]<<' ';cout<<endl;cout.flush(); string s;cin>>s;return s[0]=='Y' ? 1:0; } void guess(int x) { cout<<"! "<<x<<endl;cout.flush(); string s;cin>>s; if(s[1]==')') exit(0); } int main() { ios::sync_with_stdio(false); cin>>n; repn(i,n) v.pb(i); while(v.size()>3) { int bas=v.size()/4,mo=bas+1,mocnt=v.size()%4; vector <int> vv[4]; int cur=0; rep(i,4) { if(i<mocnt) rep(j,mo) vv[i].pb(v[cur++]); else rep(j,bas) vv[i].pb(v[cur++]); } int r1=ask(comb(vv[0],vv[1])),r2=ask(comb(vv[0],vv[2])); if(r1&&r2) v=comb(vv[0],comb(vv[1],vv[2])); else if(r1&& !r2) v=comb(vv[0],comb(vv[1],vv[3])); else if(!r1&&r2) v=comb(vv[0],comb(vv[2],vv[3])); else v=comb(vv[1],comb(vv[2],vv[3])); } if(v.size()==3) { int r1=ask({v[0]}),r2=ask({v[1]}),r3=ask({v[1]}),r4=ask({v[0]}); if(r2==r3) { if(r2==1) guess(v[1]); else guess(v[0]),guess(v[2]); } else if(r1==r4) { if(r1==1) guess(v[0]); else guess(v[1]),guess(v[2]); } else guess(v[0]),guess(v[2]); } else rep(i,v.size()) guess(v[i]); return 0; }
E2. Joking (Hard Version)
懒得看题解了,不会。
F. Kazaee
很多不同种类的数,判断是否都合法,取模,考虑哈希(提到哈希大家应该都会做了吧)。
先把序列中所有出现过的数都离散化。给每一种值分配一种随机的权值,一个区间的哈希值就是这个区间内所有数的权值之和。这里哈希值不对任何东西取模,也不能自然溢出,要保证分配的权值不太大(比如1e9就差不多)。对于一个合法的区间,容易发现它的哈希值对当前询问中的k取模一定为0;如果这个区间不合法,发现可以认为这个区间的哈希值是随机的,也就是对k取模的值在中随机,如果哈希值对k取模的值确实不为0,我们就发现了这个区间不合法。但是不合法区间的哈希值也可能刚好与k同余,比如k=2时甚至有的概率发生。那我们干脆对这个序列多做几次哈希筛不合法区间的过程,比如30次,那一个不合法区间没被筛出来的概率就是,所有不合法区间都被筛出来的概率是,这个概率是0.9999这个级别的,如果还wa可能是你脸太黑了
树状数组维护哈希值,总时间复杂度。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <int,int> #define fi first #define se second #define mpr make_pair #define pb push_back using namespace std; mt19937 rnd(114514); int n,q,a[300010],bad[300010],aa[300010]; LL H[600010]; vector <int> dsc; vector <pair <pii,pii> > que; namespace bit { LL dat[300010]; LL lowbit(LL k){return k&-k;} void upd(LL k,LL val) { while(k<=n) { dat[k]+=val; k+=lowbit(k); } } LL get(LL k) { LL ret=0; while(k>0) { ret+=dat[k]; k-=lowbit(k); } return ret; } } int main() { cin>>n>>q; rep(i,n) scanf("%d",&a[i]),dsc.pb(a[i]); int x,y,z; rep(i,q) { scanf("%d",&x); if(x==1) { scanf("%d%d",&x,&y);--x;dsc.pb(y); que.pb(mpr(mpr(1,0),mpr(x,y))); } else { scanf("%d%d%d",&x,&y,&z);--x;--y; que.pb(mpr(mpr(2,x),mpr(y,z))); } } sort(dsc.begin(),dsc.end());dsc.erase(unique(dsc.begin(),dsc.end()),dsc.end()); rep(i,n) a[i]=lower_bound(dsc.begin(),dsc.end(),a[i])-dsc.begin(); rep(i,q) if(que[i].fi.fi==1) que[i].se.se=lower_bound(dsc.begin(),dsc.end(),que[i].se.se)-dsc.begin(); rep(ti,30) { rep(i,dsc.size()) H[i]=rnd(); rep(i,n+3) bit::dat[i]=0; rep(i,n) aa[i]=a[i],bit::upd(i+1,H[a[i]]); rep(i,q) { if(que[i].fi.fi==1) { bit::upd(que[i].se.fi+1,-H[aa[que[i].se.fi]]); aa[que[i].se.fi]=que[i].se.se; bit::upd(que[i].se.fi+1,H[aa[que[i].se.fi]]); } else if(bad[i]==0) { LL val=bit::get(que[i].se.fi+1)-bit::get(que[i].fi.se); if(val%que[i].se.se!=0) bad[i]=1; } } } rep(i,q) if(que[i].fi.fi==2) puts(bad[i] ? "NO":"YES"); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话