求点赞
A. Maxmina
首先序列全0的情况肯定是NO。否则,如果\(k\ge 3\),则在序列中随便找一个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
我们把\(a_j+=a_i\)的操作称为对i的操作。我们是不会先\(a_j+=a_i\),然后再对\(a_j\)操作的,因为这样不如直接把\(a_i\)加到\(a_j\)最后加到的那个位置去。所以我们操作的对象只能是初始序列中,没被任何东西加过的元素。发现对于一个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开头的后缀\(\cdots\)后缀+n的操作,我们给以1开头的后缀。这样可以保证每个数前面都没有比他大的数。以上"以x开头的后缀"中的x均指序列中的初始值。
时间复杂度\(O(n)\)。
D. Paths on the Tree
首先每条路径的终点一定是叶子,因为多往下延伸一点总能多点贡献。令\(f_{i,j}\)表示以i为根的子树内,一共有j条路径的最大贡献。按照题目中的要求,如果一共有s个儿子,每个儿子都会分到\(\lfloor\frac js\rfloor\)或者\(\lceil\frac js\rceil\)条路径,且取到每种值的儿子数量是固定的,假设有\(cnt\)个儿子取到\(\lceil\frac js\rceil\)条。以记忆化搜索的方式进行dp转移,转移的时候对所有儿子的\(f_{u,\lceil\frac js\rceil}-f_{u,\lfloor\frac js\rfloor}\)排序,取这个值最大的cnt个儿子取\(\lceil\frac js\rceil\)条路径即可。手算一下会发现,对于任意节点i,会被搜索到的\(f_{i,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 <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;
}
E1. Joking (Easy Version)
你操作数卡那么紧干嘛,哎哟~
我觉得这题挺难的,为啥小学生都会做啊
不过据说后面几题都是偷的某个伊朗比赛(伊朗人偷伊朗题是吧),最近CF怎么净出幺蛾子
为避免混淆,首先明确定义:一个询问的返回结果有两种:YES(Y),NO(N);一个询问的正确性有两种,真(T) 和 假(F)。
令当前n的可能值的集合为s。当\(|s|\ge 4\)时,把s尽量均匀地分成4份,\(s1,s2,s3,s4\)。使用两次询问,分别问\(\{s1 \cup s2\}\)和\(\{s1\cup s3\}\)。得到返回结果后,讨论这两个询问的正确性是TT、TF还是FT。比如当返回结果为YN时,正确性为TT则目标在s2内;正确性为TF则目标在s1内;正确性为FT则目标在s4内,所以应该淘汰s3。其他三种情况推理类似,具体来说:返回结果为YY淘汰s4,为YN淘汰s3,为NY淘汰s2,为NN淘汰s1。所以每2个这样的询问可以让s的大小大约乘\(\frac 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(能过)\),反正不超过\(38\cdot 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 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,k-1]\)中随机,如果哈希值对k取模的值确实不为0,我们就发现了这个区间不合法。但是不合法区间的哈希值也可能刚好与k同余,比如k=2时甚至有\(\frac 12\)的概率发生。那我们干脆对这个序列多做几次哈希\(\to\)筛不合法区间的过程,比如30次,那一个不合法区间没被筛出来的概率就是\(\frac 1{2^{30}}\),所有不合法区间都被筛出来的概率是\((1-\frac1{2^{30}})^q\),这个概率是0.9999这个级别的,如果还wa可能是你脸太黑了
树状数组维护哈希值,总时间复杂度\(O(30\cdot qlogn)\)。
点击查看代码
#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;
}