[题解] Codeforces Global Round 23 1746 A B C D E1 F 题解

点我看题

求点赞

A. Maxmina

首先序列全0的情况肯定是NO。否则,如果k3,则在序列中随便找一个1,把他左边和右边分别用第一种操作不断缩,直到序列长度为k为止,最后用一次2操作变成一个1;如果k=2,直接不断用2操作把序列缩成一个元素即可。所以最后的结论就是只要序列中有1就是YES,否则是NO。

时间复杂度O(n)

点击查看代码
#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

我们把aj+=ai的操作称为对i的操作。我们是不会先aj+=ai,然后再对aj操作的,因为这样不如直接把ai加到aj最后加到的那个位置去。所以我们操作的对象只能是初始序列中,没被任何东西加过的元素。发现对于一个0的操作相当于是把他删除。如果还要对一些1操作,那肯定是操作原序列中最靠前的一些1比较好。假设我们对k个1进行操作,这些1应该去优先填补第i+1个1之后的那些0。如果填补不完,则必须对剩下的0操作,把它们删掉。枚举k,用前缀和计算即可。

时间复杂度O(n)

点击查看代码
#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均指序列中的初始值。

时间复杂度O(n)

点击查看代码
#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

首先每条路径的终点一定是叶子,因为多往下延伸一点总能多点贡献。令fi,j表示以i为根的子树内,一共有j条路径的最大贡献。按照题目中的要求,如果一共有s个儿子,每个儿子都会分到js或者js条路径,且取到每种值的儿子数量是固定的,假设有cnt个儿子取到js条。以记忆化搜索的方式进行dp转移,转移的时候对所有儿子的fu,jsfu,js排序,取这个值最大的cnt个儿子取js条路径即可。手算一下会发现,对于任意节点i,会被搜索到的fi,j只有最多2种,也就是j的合法取值只有最多2种。我一开始用map存j居然T了,所以最好先把每个i的可能的j取值先预处理出来。

时间复杂度O(nlogn)

点击查看代码
#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时,把s尽量均匀地分成4份,s1,s2,s3,s4。使用两次询问,分别问{s1s2}{s1s3}。得到返回结果后,讨论这两个询问的正确性是TT、TF还是FT。比如当返回结果为YN时,正确性为TT则目标在s2内;正确性为TF则目标在s1内;正确性为FT则目标在s4内,所以应该淘汰s3。其他三种情况推理类似,具体来说:返回结果为YY淘汰s4,为YN淘汰s3,为NY淘汰s2,为NN淘汰s1。所以每2个这样的询问可以让s的大小大约乘34。用下面的代码发现最多需要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,连续问以下四个问题:{1},{2},{2},{1}。我们的目标是淘汰1,2,3中的任意一个数,这样接下来可以猜两次达到目标。先看中间两个询问的返回结果,如果相同,则这两个正确性必须都是T,这就至少淘汰了一个数了;否则中间两个询问正确性必是一真一假,这时再看边上两个询问,如果返回结果相同,则也必须都是T,成功淘汰至少一个数;剩下的情况就是正确性为YNYN或NYNY了(两种对称的),用和上面类似的方法分类讨论一下,发现答案不可能是2,成功淘汰一个。总操作次数似乎最多刚好82次。

我一开始以为easy version是不用可以猜两次的特殊条件的,结果发现连n=2都做不了

时间复杂度O(),反正不超过38n

点击查看代码
#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取模的值在[0,k1]中随机,如果哈希值对k取模的值确实不为0,我们就发现了这个区间不合法。但是不合法区间的哈希值也可能刚好与k同余,比如k=2时甚至有12的概率发生。那我们干脆对这个序列多做几次哈希筛不合法区间的过程,比如30次,那一个不合法区间没被筛出来的概率就是1230,所有不合法区间都被筛出来的概率是(11230)q,这个概率是0.9999这个级别的,如果还wa可能是你脸太黑了

树状数组维护哈希值,总时间复杂度O(30qlogn)

点击查看代码
#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;
}
posted @   LegendStane  阅读(306)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示