[题解] Codeforces 2240C. 博弈状态刻画与异或计数
[题解] Codeforces 2240C. 博弈状态刻画与异或计数
TAG: 博弈论,异或,构造,位运算,Codeforces
题目链接: 点击此处打开题目
题目大意
给定一个非负整数数组 \( a \)。每次操作需要选择一个非全零数组 \( b \),满足 \( 0\le b_i\le a_i \) 且所有 \( b_i \) 的异或和为 \( 0 \),随后令 \( a_i\leftarrow a_i-b_i \)。
双方轮流操作,无法操作者失败。要求统计 Alice 第一步有多少种合法选择,可以在双方最优策略下保证获胜。
核心思路
1. 先找终止状态
一开始不要急着计数,先判断什么状态无法继续操作。
若当前数组中没有正数,显然无法选出非全零的 \( b \)。
若当前数组中恰好有一个正数 \( a_k \),其他位置都只能取 \( b_i=0 \)。如果要让 \( b \) 非全零,只能令 \( b_k>0 \),但此时所有 \( b_i \) 的异或和就是 \( b_k\neq0 \),仍然不合法。
因此,正数个数不超过 \( 1 \) 的状态一定无法操作。
反过来,若数组中至少有两个正数,可以在其中两个位置分别取 \( 1 \),其他位置取 \( 0 \)。由于
所以至少存在一次合法操作。
不过“存在合法操作”还不足以说明当前状态必胜,我们还要证明它可以一步走到必败状态。
2. 从任意非终止状态走到终止状态
设当前数组的整体异或和为
我们尝试在操作后只留下第 \( k \) 个位置为正。
对于所有 \( i\neq k \),必须把 \( a_i \) 全部减完,因此令
为了使整个 \( b \) 的异或和为 \( 0 \),第 \( k \) 个位置的取值被唯一确定为
操作后,第 \( k \) 个位置变为
要让它仍然为正,必须满足
当 \( S=0 \)
直接取 \( b_i=a_i \),即可把整个数组清零。由于 \( b \) 的异或和正好是 \( S=0 \),所以这是合法操作。
当 \( S\neq0 \)
设 \( S \) 的最高位 \( 1 \) 在第 \( p \) 位。
因为所有 \( a_i \) 的异或结果在第 \( p \) 位为 \( 1 \),所以至少存在一个 \( a_k \),其第 \( p \) 位也为 \( 1 \)。
比较 \( a_k \) 与 \( a_k\oplus S \):
- 比第 \( p \) 位更高的位置完全相同;
- 第 \( p \) 位由 \( 1 \) 变成 \( 0 \)。
因此一定有
于是可以只留下第 \( k \) 个位置为正,把状态变成无法继续操作的状态。
综上:
正数个数不超过 \( 1 \) 的状态是必败状态;正数个数至少为 \( 2 \) 的状态是必胜状态。
3. 将博弈问题转化为计数问题
Alice 第一步想保证获胜,就必须把当前状态变成必败状态,也就是操作后至多剩下一个正数。
因此只需统计两类操作。
第一类:操作后全部为零
此时只能选择
该方案合法当且仅当
所以当 \( S=0 \) 时,这一类恰好贡献 \( 1 \) 种方案。
第二类:操作后只留下第 \( k \) 个位置
前面已经推出,这种方案中每个 \( b_i \) 都被唯一确定:
它合法并且确实留下一个正数,当且仅当
所以每个满足该条件的下标 \( k \),恰好对应一种获胜操作。
4. 利用最高位简化判断
当 \( S\neq0 \) 时,设其最高位 \( 1 \) 为第 \( p \) 位。
表达式
是否成立,只由最高发生变化的第 \( p \) 位决定:
- 若 \( a_i \) 的第 \( p \) 位为 \( 1 \),异或后该位变成 \( 0 \),数值变小;
- 若 \( a_i \) 的第 \( p \) 位为 \( 0 \),异或后该位变成 \( 1 \),数值变大。
因此答案就是第 \( p \) 位为 \( 1 \) 的 \( a_i \) 数量。
代码中也可以不显式寻找最高位,直接判断
(sum^a[i])<a[i]
即可。
5. 边界情况
当 \( n=1 \) 时,题目初始只有一个正数。任何非全零的 \( b \) 都只有一个正数,其异或和不可能为 \( 0 \),因此答案为 \( 0 \)。
当 \( S=0 \) 时,对于每个 \( i \) 都有
此时不能把条件写成小于等于,否则会把“全部清零”错误地按每个下标重复统计。判断必须使用严格小于。
最终结论为:
复杂度
- 时间复杂度:\( O(n) \)。需要遍历数组计算整体异或和,并再次遍历统计满足条件的位置。
- 空间复杂度:\( O(n) \)。数组用于保存输入,以便得到整体异或和后再次判断每个位置。
代码前说明
关键变量
sum:整个数组的异或和 \( S \);x:若只留下第 \( i \) 个位置,则该位置需要减去的数 \( b_i=S\oplus a_i \);cnt:能够一步走到必败状态的操作数量。
核心判断
若只留下第 \( i \) 个位置,则必须有
操作后还要保证该位置为正,因此代码判断为
if((sum^a[i])<a[i]) cnt++;
这里必须是严格小于,不能写成小于等于。
完整代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define INF 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define rep(i,j,k) for (int i=j;i<=k;++i)
#define per(i,j,k) for (int i=j;i>=k;--i)
#define fi first
#define se second
//#define mid (l+r>>1)
//#define ls x<<1
//#define rs x<<1|1
//int up=*max_element(a+1,a+n+1);
const double eps=1e-6;
const double PI=acos(-1);
const int mod=998244353;
const int N=1e6+5;
int a[N];
void solve(){
int n;cin>>n;
int sum=0;//整个数组的异或和
for(int i=1;i<=n;i++){
cin>>a[i];
sum^=a[i];
}
//只有一个正数时,不存在非全零且异或和为 0 的操作
if(n==1){
cout<<0<<endl;
return;
}
//唯一的获胜操作是令 b=a,将整个数组清零
if(sum==0){
cout<<1<<endl;
return;
}
int cnt=0;
for(int i=1;i<=n;i++){
//只留下第 i 个位置时,该位置需要减去的数量
int x=sum^a[i];
//必须严格小于 a[i],操作后第 i 个位置才仍然为正
if(x<a[i]) cnt++;
}
cout<<cnt<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t=1;cin>>t;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号