Codeforces Round 1034 (Div. 3) A-G 题解

本来可以AK的,就因为 D 这个__东西,害得我G都没看,我赛时要是看G了直接秒掉。
算法题基本上对我来说不难,主要是思维题。全场8000多人过我都想不出来,我思维还是太菜了。

全员排行(Friends only):
微信截图_20250702180645

Rated成员排行(Friends only):
微信截图_20250702180708

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;
}
```![微信截图_20250702180645](https://img2024.cnblogs.com/blog/3531201/202507/3531201-20250702183525290-1261312873.png)
posted @ 2025-07-02 18:37  AKDreamer_HeXY  阅读(178)  评论(0)    收藏  举报