Codeforces Round 1034 (Div. 3) A-G 题解
本来可以AK的,就因为 D 这个__东西,害得我G都没看,我赛时要是看G了直接秒掉。
算法题基本上对我来说不难,主要是思维题。全场8000多人过我都想不出来,我思维还是太菜了。
全员排行(Friends only):

Rated成员排行(Friends only):

A. Blackboard Game
题意
黑板上有 \(n\) 个整数:\(0\sim n-1\),每次,\(\text{Alice}\) 先从剩下的数中选一个数 \(a\) 并把它擦掉,\(\text{Bob}\) 再选一个数 \(b\),使得 \(a+b\bmod3=0\),然后擦掉,最后一个不能选数的人输。若双方都已最佳策略玩游戏,赢得比赛的人是谁?
思路
将 \(n\) 个数分为 \(4\) 类:\(A,B,C,D\),分别表示 \(\bmod4=0,1,2,3\)。每次 \(\text{Alice}\) 从 \(A\) 中选,\(\text{Bob}\) 就只能从 \(C\) 中选,记为 \((A,C)\),同理 \((C,A),(B,D),(D,B)\),那么只有当 \(A,B,C,D\) 的个数相同时,即 \(n\bmod4=0\) 时,\(\text{Bob}\) 才有可能获胜,其余情况 \(\text{Alice}\) 胜。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
int main(){
int T;
cin>>T;
while(T--){
int n;
cin>>n;
if(n%4==0){
cout<<"Bob\n";
}else{
cout<<"Alice\n";
}
}
return 0;
}
B. Tournament
题意
\(n\) 个人,第 \(i\) 个实力为 \(a_i\)。每次你可以任选两个人,移走实力较差的那个人(若两人相同,任意移走一个),直到只剩下 \(k\) 个。问:是否存在一种方案,使得 \(j\) 个人能留到最后。
思路
\(k\ge 2\) 时,可以一直不选 \(j\),\(j\) 就一定不会被移走;否则,\(k=1\),\(j\) 一定会被选中,所以它必须得是实力最强的那一个,即 \(a_j=mx\),其中 \(mx\) 表示 \(a_i\) 的最大值。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
int v[maxn];
void solve(){
int n,j,k;
cin>>n>>j>>k;
for(int i=1;i<=n;i++) cin>>v[i];
if(k>=2){
cout<<"YES\n";
return;
}
int val=v[j];
sort(v+1,v+1+n);
if(v[n]==val){
cout<<"YES\n";
}else{
cout<<"NO\n";
}
}
int main(){
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
C. Prefix Min and Suffix Max
题意
你有一个所有数字都不同的长度为 \(n\) 的数组 \(a\),在一次操作中:
- 可以选择一个前缀,将这个前缀全部删掉,替换成这些数的最小值
- 可以选择一个后缀,将这个前缀全部删掉,替换成这些数的最大值
可以选择整个数组。
对于每个 \(a_i\),是否存在一种方案,使得执行任意次操作以后,整个数组只包含一个元素且与 \(a_i\) 相等。可以输出 1,不能输出 0。
思路
对于每个 \(a_i\),先考虑比较明显的一种方案:
- 选择前缀 \(1\sim i-1\),后缀 \(i+1 \sim n\),此时序列变成 \(3\) 个数:设为 \(a,b,c\),可以发现,\(a>b>c,a<b>c\) 都是可以的,也就是除了 \(a<b<c\) 的情况,其它都可以将它变为 \(b\)。
接下来,我们单独研究 \(a<b<c\) 的情况,考虑原数组,相当于 \(a_i\) 大于前面的最小值,且小于后面的最大值,也就是说前缀最多选到 \(i-1\),后缀最多选到 \(i+1\),否则就把 \(a_i\) 覆盖了,然而,这样总是无法将长度缩小到 \(1\)。所以当且仅当 \(a<b<c\) 时,不可以;其余情况都可以。
前缀最小值和后缀最大值可以用数组预处理,也可以用 set 维护、
C++ 代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
int v[maxn];
void solve(){
int n;
cin>>n;
multiset<int> p,s;
for(int i=1;i<=n;i++){
cin>>v[i];
s.insert(v[i]);
}
p.insert(inf); s.insert(0);//边界情况,防止1和n的时候出错
string ans="";
for(int i=1;i<=n;i++){
s.erase(s.find(v[i]));
int prv=*p.begin();//前缀最小值
int nxt=*s.rbegin();//后缀最大值
if(prv<v[i]&&v[i]<nxt){
ans+="0";
}else{
ans+="1";
}
p.insert(v[i]);
}
cout<<ans<<endl;
}
int main(){
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
D. Binary String Battle
题意
有一个长度为 \(n\) 的 \(01\) 字符串 \(s\),每次按顺序进行以下操作:
-
\(\text{Alice}\) 选择一个长度为 \(k\) 的子序列,并把它们全部变成
0; -
\(\text{Bob}\) 选择一个长度为 \(k\) 的字串,并把它们全部变成
1。
若双方都以最佳策略操作,问:是否可以有某一时刻,整个字符串都是 0。
若可以,输出 Alice,否则输出 Bob。
思路
结论:若 1 的个数 \(\le k\) 或 \(2k>n\),\(\text{Alice}\) 胜。
第一个不用证明了,一次就结束了。
第二个引述自 \(\color{orange}{\text{UKBwyx}}\) 大佬的证明:
- 若 \(2k>n\),假设上一轮是全
1(对 \(\text{Alice}\) 最差的局面),可以从左右端分别开始选,左右边的0尽可能一样多。而每次 \(\text{Bob}\) 只能顾及一边,所以若干次后,可以造出两边各有 \(n-k\) 个0的局面。不管 \(\text{Bob}\) 选哪边,都至少有 \(n-k\) 个0,即只有 \(m\) 个1,\(\text{Alice}\) 就可以获胜。 - 否则,\(\text{Bob}\) 可以在场上任选一个是
1的位置,不可能构造出上述情况。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
void solve(){
int n,k;
string s;
int cnt=0;
cin>>n>>k>>s;
s=" "+s;
for(int i=1;i<=n;i++){
if(s[i]=='1') cnt++;
}
if(cnt<=k){
cout<<"Alice\n";
}else if(2*k>n){
cout<<"Alice\n";
}else{
cout<<"Bob\n";
}
}
int main(){
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
E. MEX Count
题意
定义 \(\text{MEX}\) 表示一个集合中未出现过的最小非负整数。数组 \(a\) 长度为 \(n\),\(0\le a_i\le n\),问:对于每个整数 \(k\) \((0\le k\le n)\),从 \(a\) 种删除恰好 \(k\) 个数可以获得多少种不同的 \(\text{MEX}\)。
思路
设 \(cnt_i\) 表示 \(i\) 在数组中出现的次数,\(ans_k\) 表示每个 \(k\) 对应的答案。
我们可以考虑:对于每一个 \(\text{MEX}=i(0\le i\le n)\),删除多少个数可以满足此状况。计删除个数为 \(f\)。
可以想一下,要是 \(\text{MEX}\) 为 \(i\) 的条件是什么:是 \(0\sim i-1\) 的所有数每个至少出现一次,且 \(i\) 没有出现。
对于已经满足条件 “是 \(0\sim i-1\) 的所有数每个至少出现一次”:
- \(f\) 的最小值,就是将所有的 \(i\) 删掉,即 \(f_{\min}=cnt_i\);
- \(f\) 的最大值,在把所有的 \(i\) 删掉的基础上,把 \(>i\) 的全部删掉,然后把 \(0\sim i-1\) 每个数只保留一个,即 \(f_{\max}=n-i\)。
此时,这个 \(i\) 对所有 \(f_{\min}\le k\le f_{\max}\) 的 \(k\) 贡献为 \(1\),也就是把 \(ans_{f_{\min} \sim f_{\max}}\) 都 \(+1\)。
再考虑上面的假设,如果不满足“是 \(0\sim i-1\) 的所有数每个至少出现一次”,则无论删掉多少个数, \(\text{MEX}\) 都不可能为 \(i\),则这个 \(i\) 对答案的贡献一定为 \(0\)。
接下来讲一下如何维护上面说的区间 \(+1\) 操作:差分,计 \(C_i=ans_i-ans_{i-1}(i>0)\)
则这个区间加操作转化成了把 \(C_{f_{\min}}\) 加一,\(C_{f_{\max}+1}\) 减一。
然后计算 \(ans\) 数组:对于 \(i=0\),\(ans_i=C_i\),对于 \(i>0\),\(ans_i=ans_{i-1}+C_i\)。
最后输出所有 \(ans_i(i={0\sim n})\) 即可。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
int cnt[maxn];
int ans[maxn];
void solve(){
int n;
cin>>n;
for(int i=0;i<=n;i++) cnt[i]=0;ans[i]=0;//多测记得清空
for(int i=1;i<=n;i++){
int x; cin>>x;
cnt[x]++;
}
//此处我不想再开一个数组C了,直接在ans上操作
for(int i=0;i<=n;i++){
ans[cnt[i]]++;
ans[n-i+1]--;
if(cnt[i]==0) break;//有一个不可以,后面的一定都不可以
}
for(int i=1;i<=n;i++) ans[i]+=ans[i-1];
for(int i=0;i<=n;i++) cout<<ans[i]<<" ";
cout<<endl;
}
int main(){
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
F. Minimize Fixed Points
题意
构造一个长度为 \(n\) 的排列 \(p\),满足以下条件:
- 对于 \(2\le i\le n\),\(\gcd(p_i,i)>1\);
- 对于 \(1\le i\le n\),使 \(p_i=i\) 的数量最小
思路
首先,\(1\) 肯定排在第一位,不然就不满足条件 \(1\) 了。
然后我们可以想到,对于大于 \(\dfrac{n}{2}\) 的质数 \(k\),一定满足 \(p_k=k\):
- 证明:如果不等的话,比它小的不可能一定与之互质,不满足条件 \(1\),比它大的至少得是 \(2k\) 才能不互质,但是因为 \(\dfrac{n}{2}>k\) ,所以 \(2k>n\),他就不是排列了。
初始,令 \(p\) 全部为 \(0\)。
剩下的数我们从最大的 \(\le \dfrac{2}{n}\) 的质数开始,从大往小考虑:
- 对于质数 \(k\) :找到所有的倍数 \(2k,3k,4k,...,rk\) ,且满足\(p_{ik}=0\) 的 \(ik\),直到最大的 \(r\) 使得 \(rk\le n\),然后令 \(p_{ik}=jk\),\(j\) 为上一个满足条件的系数。特别地,\(p_k=rk\)。
- 简单地说,就是把所有是 \(k\) 的倍数的且没有用过的数,全员向左平移,然后把最左边的移到最右边去。
- 举个例子,比如说 \(n=13,k=3\) 时,令 \(p_6=3\),\(p_{9}=6\),\(p_{12}=9\),最后,\(p_3=12\)。
- 可以证明,每个数的 \(r\ge 2\),因为系数等于就算别的数用过了某个自己也可以用的数, \(k^2,k^3,...\) 这种数是一定不会被占用的。
最后输出时,若 \(p_i=0\),输出 \(i\) 即可;否则输出 \(p_i\)。
质数直接提前用埃式筛预处理好就行了。
时间复杂度约为:\(O(n \log n)\)。
C++ 代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=200005;
int ans[maxn];
bool prime[maxn];
vector<int> p;
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
ans[i]=0;
}
int pos=lower_bound(p.begin(),p.end(),n/2)-p.begin();
for(int i=pos;i>=1;i--){
if(p[i]>n/2) continue;
int prv=p[i];
for(int j=2*p[i];j<=n;j+=p[i]){
if(ans[j]==0){
ans[j]=prv;
prv=j;
}
}
ans[p[i]]=prv;
}
//这一步多余了,相当于把2单拎出来,可以直接和上面合并起来写
int prv=2;
for(int i=4;i<=n;i+=2){
if(ans[i]==0) ans[i]=prv,prv=i;
}
ans[2]=prv;
for(int i=1;i<=n;i++){
if(ans[i]==0) cout<<i<<" ";
else cout<<ans[i]<<" ";
}
cout<<endl;
}
signed main(){
int n=100005;
for(int i=2;i<=n;i++){
prime[i]=1;
}
for(int i=2;i<=n;i++){
if(prime[i]){
for(int j=i*i;j<=n;j+=i){
prime[j]=0;
}
}
}
for(int i=2;i<=n;i++){
if(prime[i]){
p.pb(i);
}
}
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
G. Modular Sorting
题意
有一个长度为 \(n\) 的数组,每个 \(0\le a_i<m\)。
维护两种操作:
1 i x:把 \(a_i\) 的值改为 \(x\)2 k:每次操作可以任选一个 \(i\),然后将 \(a_i\) 改为 \((a_i+k)\bmod m\)。问:是否存在一种方式,使得 \(a\) 单调不减,若存在,输出YES,否则输出NO。
注意:操作 \(2\) 并没有真的改变原数组,只是一个假设。
思路
根据裴蜀定理,经过若干次操作后,每个 \(a_i\) 可以变动 \(\gcd(m,k)\) 的倍数(必须保持在 \(0\sim m-1\) 之间)。换句话说,无论怎么变 \(a_i \bmod \gcd(m,k)\) 总是不变的。
- 比如 \(m=10,k=4\) 时,\(\gcd(m,k)=2\),这时 \(5\) 可以且只可以变成 \(1,3,7,9\) 或不变。
有了这个结论以后,我们可以考虑枚举 \(\gcd\),\(\gcd(m,k)\) 一定时 \(m\) 的因数,所以要枚举 \(m\) 的因数。
然后对于第 \(i\) 个因数 \(fac_i\) ,记录 \(p_{i,j}=v_j\bmod fac_i\),要想使这个递增,可以使每个元素加上 \(0\) 个或多个 \(fac_i\),最多的哪个要加多少个呢:
- 很明显,如果 \(p_i>p_{i-1}\),就要加一次,最后那一个就要加上 \([满足 p_i>p_{i-1} 的数量总和]\) 个 \(fac_i\)。将上述个数记为 \(num_i\)。
设 \(to_k\) 表示 \(k\) 为 \(m\) 的第几个因数。
所以,先预处理出 \(p\) 和 \(num\),然后:
- 对于第 \(1\) 种操作,我们要遍历所有的因数,把原来这一位影响答案的数量减去,再把改完以后这一位影响答案的数量加上。
- 对于第 \(2\) 种操作,设 \(to_{\gcd(k,m)}=c\) ,如果 \(p_{c}\) 的最后一个元素加上 \(num_c\times c\) 小于 \(m\) 就可以,否则就不行。
总时间复杂度:\(O(\sqrt m+n\sqrt m +q\sqrt m)\)。
C++ 代码
#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define all(x) x.begin(),x.end()
#define sz(v) (int)v.size()
using namespace std;
const int inf=3e18;
const int maxn=500005;
int v[maxn],to[maxn];
vector<int> p[300];
int num[300];
void solve(){
int n,m,q;
cin>>n>>m>>q;
for(int i=0;i<=m;i++) to[i]=0;
for(int i=1;i<=n;i++) cin>>v[i];
vector<int> fac;
for(int i=1;i*i<=m;i++){
if(m%i==0){
fac.pb(i);
if(i*i!=m) fac.pb(m/i);
}
}
sort(all(fac));
for(int i=0;i<sz(fac);i++){
to[fac[i]]=i;
num[i]=0;
p[i].clear();
p[i].resize(n+2);
for(int j=1;j<=n;j++){
p[i][j]=v[j]%fac[i];
if(p[i][j]<p[i][j-1]){
num[i]++;
}
}
p[i][n+1]=inf;//最后一位改变后,它的后面一位不会对答案有影响,防止第65行和68时计算出错
}
while(q--){
int opt;
cin>>opt;
if(opt==1){
int pos,x;
cin>>pos>>x;
v[pos]=x;
for(int i=0;i<sz(fac);i++){
if(p[i][pos]<p[i][pos-1]) num[i]--;
if(p[i][pos+1]<p[i][pos]) num[i]--;
p[i][pos]=x%fac[i];
if(p[i][pos]<p[i][pos-1]) num[i]++;
if(p[i][pos+1]<p[i][pos]) num[i]++;
}
}else{
int k;
cin>>k;
k=__gcd(m,k);
if(p[to[k]][n]+num[to[k]]*k<m){
YES;
}else{
NO;
}
}
}
}
signed main(){
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
```

浙公网安备 33010602011771号