CF 782
A
题意:
给\(a\)个红球和\(b\)个蓝球,让你摆成一排,连续红球数量的最大值最小。
题解:
可以去二分,但没必要,数量就是\(x=\lceil\frac{a}{b+1}\rceil\)
构造就是满\(x\)个红球就放一个蓝球,直到没红球了,写的很乱就不放了。
B
题意:
给一个长度为\(n\)的\(01\)串,你要做恰好\(k\)次操作,每次选一个位置,把这个位置之外的所有位置都取反。
求最后字典序最大的结果。
题解:
考虑贪心,我们先让前面的数字最大,尽量成为\(1\)。
怎么做呢,比如第一位是\(1\),那么我们很想让不选位置一的操作次数是偶数,这样可以保证第一位比较大。
所以如果\(k\)是奇数,把\(k\)在这里用一次,否则不用动,把\(k\)直接留给后面去用。
然后下一位,下一位的值是本来的值加上前面使用次数的影响。
问题规模减小。最后把剩下的次数都丢给最后一位消化。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=4e5+10,mod=998244353,inf=2e9;
int n,m,tot;
char s[N];
int a[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>m;tot=0;
cin>>(s+1);
for(int i=1;i<=n;++i) a[i]=0;
if(!m)
{
cout<<(s+1)<<'\n';
for(int i=1;i<=n;++i) cout<<0<<" \n"[i==n];
continue;
}
for(int i=1;i<=n;++i)
{
if(tot&1)
{
if(s[i]=='1') s[i]='0';
else s[i]='1';
}
if(m<=0||i==n) continue;
if(s[i]=='1')
{
if(m%2==1)
{
--m;++a[i];++tot;
}
}
else
{
if(m%2==0)
{
--m;++a[i];++tot;
}
s[i]='1';
}
}
a[n]+=m;
cout<<(s+1)<<'\n';
for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
C
题意:
有个王国位于\(x_0=0\),有\(n\)个城市,坐标是\(x_1,x_2,…,x_n\),满足\(x_1<x_2<…<x_n\)
要求按顺序征服\(n\)个城市
如果当前首都是\(i\),要征服\(j\),代价是\(b*|x_i-x_j|\)
如果当前首都是\(i\),要迁都\(j\),代价是\(a*|x_i-x_j|\),但目的地必须被征服。
求征服所有城市的最小代价。
题解:
考虑如果最后最优答案的首都在\(k\),那么我们一定会刚征服\(k\)就迁都过去。
如果不搬过去,这一段路会贡献\((n-i+1)*b*|x_i-x_{i-1}|\)的代价,因为后面每座城市都要走一遍。
如果搬过去,这一段路会贡献\((a+b)*|x_i-x_{i-1}|\),先打下来,再搬过去。
枚举最终那个位置作为首都,然后取对答案\(min\)。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=4e5+10,mod=998244353,inf=2e9;
int n,a,b,ans;
int p[N],s[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>a>>b;ans=inf*inf;
for(int i=1;i<=n;++i)
{
cin>>p[i];
}
s[n+1]=0;
for(int i=n;i>=1;--i) s[i]=s[i+1]+p[i];
for(int i=0;i<=n;++i)
{
ans=min(ans,(a+b)*p[i]+(s[i+1]-p[i]*(n-i))*b);
}
cout<<ans<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
D
题意:
有一个仅有\(0/1\)构成的数组\(A\)
\(B_i\)是把\(A\)的前\(i\)位从小到大排序,其他位不动的数组
现在有数组\(C=B_1+B_2+…+B_n\)(对应位相加),还原\(A\)数组,保证有解。
题解:
倒序处理,可以发现\(A\)中的每个\(1\)都会给\(C\)中的总和\(+n\),所以最后\(A\)中\(1\)的数量就是\(C\)每一位的和除以\(n\),设为\(k\)
如果某一位\(i\)前面有\(k\)个\(1\),那么\([i-k+1,i]\)都会被\(+1\),我们可以倒着想这个过程。
如果某一位前面有\(k\)个\(1\),我们就给\([i-k+1,i]\)都减去\(1\),如果这一位减完了之后还剩下\(i-1\),那说明这一位是\(1\),这剩下的\(i-1\)是排序排不到这个位置时作出的贡献。
对于第一位要特判,因为他前面没有人给他垫,剩下的数只能第一位拿走。
这个过程可以用树状数组模拟,也可以用差分模拟。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=4e5+10,mod=998244353,inf=2e9;
int n,r,sum;
int a[N],c[N],b[N];
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n;r=sum=0;
for(int i=1;i<=n;++i)
{
cin>>c[i];
a[i]=0;
sum+=c[i];
b[i]=0;
}
int k=sum/n;
int l=n-k+1;
b[n+1]=0;
for(int i=n;i>=1&&k;--i)
{
++b[i];--b[max(0ll,i-k)];
b[i]+=b[i+1];
c[i]-=b[i];
if(c[i]>0)
{
a[i]=1;
--k;
}
else a[i]=0;
}
if(k) a[1]=1;
for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
7
0 3 4 2 3 2 7
1 1 1 0 0 1
1 1 1 0 0 1
1 1 1 0 0 1
1 1 1 0 0 1
0 1 1 1 0 1
0 0 1 1 1 1
0 0 1 1 1 1
1
6
3 4 6 3 2 6
*/
E
题意:
给一张有\(n\)个顶点,\(m\)条边的图,每条边有边权。
定义一条路径的权值:
设这条路径的起点\(u\)到终点\(v\)中间经过的边的边权是\(w_1,w_2,w_3,w_4…\)
那么这条路径的权值是\(mex\{w_1,w_1\&w_2,w_1\&w_2\&w_3,……\}\)
其中\(mex\)是集合没有出现的最小自然数。
题解:
结论:任意路径的权值不超过\(2\)。
证明:假如这个集合里有\(0\)和\(1\),那么说明二进制第零位的一,从起点一直到数字变成零为止都没有消失,所以这个集合的\(mex\)一定是\(2\)。
情况一:\(mex\)为零
枚举二进制下哪一位没有被干掉,把二进制下这一位是\(1\)的边连接的点用并查集合并。
复杂度\(O(30*n)\)
情况二:\(mex\)为一
说明集合中没有出现\(1\)数字就直接变成零了,我们假设某条路径最后一次出现奇数是二进制下第零位加某一位,枚举这个某一位,那么就可以得到一些联通块。
这些连通块内部可以保证第零位和某一位一直存在。
然后我们去枚举某个点有没有一条边,这条边的边权是偶数。这条偶数边可以直接把权值干成\(0\)。
包含这样一个点的连通块都可以作为起点所在的连通块。因为起点在这里面可以先绕一圈,把权值绕成第零位加某一位,然后再去走这条偶数边,把权值干成\(0\),后面随便走都可以了。
情况三:\(max\)为二
剩下的。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=4e5+10,mod=998244353,inf=2e9;
int n,m,k;
struct node
{
int x,y,z;
}a[N],q[N];
int f[N];
bool vis[N];
inline int find(int k){return f[k]==k?k:f[k]=find(f[k]);}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;++i)
{
cin>>a[i].x>>a[i].y>>a[i].z;
}
cin>>k;
for(int i=1;i<=k;++i)
{
cin>>q[i].x>>q[i].y;
q[i].z=-1;
}
for(int b=29;b>=0;--b)
{
for(int i=1;i<=n;++i) f[i]=i;
for(int i=1;i<=m;++i)
{
if(((a[i].z>>b)&1)==0) continue;
int tx=find(a[i].x),ty=find(a[i].y);
f[tx]=ty;
}
for(int i=1;i<=k;++i)
{
if(q[i].z!=-1) continue;
if(find(q[i].x)==find(q[i].y)) q[i].z=0;
}
}
for(int b=29;b>=1;--b)
{
for(int i=1;i<=n;++i) f[i]=i,vis[i]=0;
for(int i=1;i<=m;++i)
{
int tmp=(1<<b)|1;
if((a[i].z&tmp)!=tmp) continue;
int tx=find(a[i].x),ty=find(a[i].y);
f[tx]=ty;
}
for(int i=1;i<=m;++i)
{
if(a[i].z%2==1) continue;
int tx=find(a[i].x),ty=find(a[i].y);
vis[tx]=vis[ty]=1;
}
for(int i=1;i<=k;++i)
{
if(q[i].z!=-1) continue;
if(vis[find(q[i].x)]) q[i].z=1;
}
}
for(int i=1;i<=k;++i)
{
if(q[i].z==-1) q[i].z=2;
cout<<q[i].z<<'\n';
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
F
题意:
有一个\(1\sim n\)的排列,\(Alice\)可以每次必须选择两个数字,交换它们的位置。
有一颗\(n\)个节点的树,一开始有一块令牌在\(pos\)处,\(Bob\)每次必须选择一个相邻的节点,把它挪过去。
如果令牌位于\(pos\)处,则\(Alice\)选的数字不能有\(pos\)
\(Alice\)想把排列恢复升序,\(Bob\)想干扰他,\(Alice\)先手,谁会赢?
题解:
首先证明:如果树的直径超过三个节点,\(Alice\)一定能成功。
证明:
如果有超过\(2\)个数字不在自己该在的位置,那么每次操作至少能把一个数字放回位置。
那么我们现在剩下两个数字\(x,y\)互相占据了对方的位置,并且假设令牌在\(y\)。
我们可以把\(x\)放到\(y\)旁边的一个节点,不断地把这两个节点挪到直径的某个端点处,并且两个点相邻。
然后就比较好构造方案了,如果直径上至少有\(4\)个点,一定能操作操作把这两个点归位,证明略。
然后考虑直径不超过\(3\)个节点的树,即菊花图。
我们不妨设菊花的中心是树根,并设\(k\)是把排列归为要花的最少次数。
在此之前,我们要把排列本就有序,或\(k=1\)且可以一次换好的情况特判掉。
可以把情况分成以下几种:
\(1.\)令牌在根,根没归位:
这样\(Bob\)一定赢,\(Bob\)可以在根和根要去的地方反复横跳。
我们称这个过程为\(opt1\)
\(2.\)令牌在根,根归位了:
如果\(k\)是奇数,\(Alice\)赢,否则\(Bob\)赢。
如果\(k\)是奇数,\(Alice\)可以刚好让令牌回根后完成最后一次交换,否则\(Alice\)要做最后一次交换时令牌可以刚好卡住他,他不得不去换别的数,而别的数还要再花一次换回来。
我们称这个过程为\(opt2\)
\(3.\)令牌在没归位的叶子,根归位了:
如果\(k\)是偶数,\(Alice\)赢,否则\(Bob\)赢。
和上面恰好相反,令牌一开始不在根,要花一步去根,所以和上面一种的奇偶性刚好相反。
我们称这个过程为\(opt3\)
\(4.\)令牌在没归位的叶子,根没归位:
如果令牌在的节点刚好是根要的数字,那\(Bob\)赢,他可以在这两个节点间反复横跳,你永远不能把根归位。
否则,只要花一步把根去归位,等于是\(opt3\)
\(5.\)令牌在归位的叶子,根归位或没归位:
如果根没归位,你也可以花一步去把根归位,还是只和\(k\)有关。
类似地证明了,这个也是\(opt3\),但是要注意如果一步就可以排好要提前特判。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=4e5+10,mod=998244353,inf=2e9;
int n,pos,rt,s;
vector<int> eg[N];
int p[N];
bool vis[N];
int dep[N],maxn;
inline void dfs(int now,int fa)
{
dep[now]=dep[fa]+1;
if(dep[now]>dep[rt]) rt=now;
for(int t:eg[now])
{
if(t==fa) continue;
dfs(t,now);
}
}
inline bool check(int x){return p[x]!=x;}
int tt;
inline void work()
{
s=0;
for(int i=1;i<=n;++i) vis[i]=0;
for(int i=1;i<=n;++i)
{
if(vis[i]) continue;
vis[i]=1;
int now=p[i];
while(!vis[now])
{
tt=now;
vis[now]=1;
now=p[now];
++s;
}
}
}
inline void work1()
{
cout<<"Bob\n";
}
inline void work2()
{
if(s&1) cout<<"Alice\n";
else cout<<"Bob\n";
}
inline void work3()
{
//cout<<"!!"<<endl;
if(s&1) cout<<"Bob\n";
else cout<<"Alice\n";
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>pos;
for(int i=1;i<=n;++i)
{
eg[i].clear();
}
for(int i=1;i<n;++i)
{
int x,y;cin>>x>>y;
eg[x].emplace_back(y);
eg[y].emplace_back(x);
}
for(int i=1;i<=n;++i)
{
int x;cin>>x;
p[x]=i;
}
work();
if(!s)
{
cout<<"Alice\n";
continue;
}
if(s==1&&pos!=tt&&pos!=p[tt])
{
cout<<"Alice\n";
continue;
}
rt=0;
dfs(1,0);
dfs(rt,0);
if(dep[rt]>3)
{
cout<<"Alice"<<'\n';
}
else
{
for(int i=1;i<=n;++i)
{
if(eg[i].size()>eg[rt].size()) rt=i;
}
if(rt==pos&&check(rt)) work1();
else if(rt==pos&&!check(rt)) work2();
else if(rt!=pos&&check(pos)&&check(rt))
{
if(p[pos]==rt) work1();
else work3();
}
else if(rt!=pos&&check(pos)&&!check(rt))
{
work3();
}
else if(rt!=pos&&!check(pos)&&check(rt))
{
if(s==1) cout<<"Alice\n";
else work3();
}
else if(rt!=pos&&!check(pos)&&!check(rt))
{
if(s==1) cout<<"Alice\n";
else work3();
}
}
}
}
}
signed main()
{
// freopen("data.in","r",stdin);
// freopen("red.out","w",stdout);
red::main();
return 0;
}
/*
1
4 1
2 1
2 3
2 4
2 3 1 4
*/