[题解] 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 \)。由于

\[1\oplus1=0, \]

所以至少存在一次合法操作。

不过“存在合法操作”还不足以说明当前状态必胜,我们还要证明它可以一步走到必败状态。

2. 从任意非终止状态走到终止状态

设当前数组的整体异或和为

\[S=a_1\oplus a_2\oplus\cdots\oplus a_n. \]

我们尝试在操作后只留下第 \( k \) 个位置为正。

对于所有 \( i\neq k \),必须把 \( a_i \) 全部减完,因此令

\[b_i=a_i. \]

为了使整个 \( b \) 的异或和为 \( 0 \),第 \( k \) 个位置的取值被唯一确定为

\[b_k=\bigoplus_{i\neq k}a_i=S\oplus a_k. \]

操作后,第 \( k \) 个位置变为

\[a_k-b_k=a_k-(S\oplus a_k). \]

要让它仍然为正,必须满足

\[S\oplus a_k<a_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 \)。

因此一定有

\[a_k\oplus S<a_k. \]

于是可以只留下第 \( k \) 个位置为正,把状态变成无法继续操作的状态。

综上:

正数个数不超过 \( 1 \) 的状态是必败状态;正数个数至少为 \( 2 \) 的状态是必胜状态。

3. 将博弈问题转化为计数问题

Alice 第一步想保证获胜,就必须把当前状态变成必败状态,也就是操作后至多剩下一个正数。

因此只需统计两类操作。

第一类:操作后全部为零

此时只能选择

\[b_i=a_i. \]

该方案合法当且仅当

\[S=0. \]

所以当 \( S=0 \) 时,这一类恰好贡献 \( 1 \) 种方案。

第二类:操作后只留下第 \( k \) 个位置

前面已经推出,这种方案中每个 \( b_i \) 都被唯一确定:

\[b_i=a_i\quad(i\neq k), \]

\[b_k=S\oplus a_k. \]

它合法并且确实留下一个正数,当且仅当

\[S\oplus a_k<a_k. \]

所以每个满足该条件的下标 \( k \),恰好对应一种获胜操作。

4. 利用最高位简化判断

当 \( S\neq0 \) 时,设其最高位 \( 1 \) 为第 \( p \) 位。

表达式

\[S\oplus a_i<a_i \]

是否成立,只由最高发生变化的第 \( 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 \) 都有

\[S\oplus a_i=a_i. \]

此时不能把条件写成小于等于,否则会把“全部清零”错误地按每个下标重复统计。判断必须使用严格小于。

最终结论为:

\[\operatorname{ans}= \begin{cases} 0,&n=1,\\ 1,&n\ge2\text{ 且 }S=0,\\ \#\{i\mid(S\oplus a_i)<a_i\},&S\neq0. \end{cases} \]

复杂度

  • 时间复杂度:\( O(n) \)。需要遍历数组计算整体异或和,并再次遍历统计满足条件的位置。
  • 空间复杂度:\( O(n) \)。数组用于保存输入,以便得到整体异或和后再次判断每个位置。

代码前说明

关键变量

  • sum:整个数组的异或和 \( S \);
  • x:若只留下第 \( i \) 个位置,则该位置需要减去的数 \( b_i=S\oplus a_i \);
  • cnt:能够一步走到必败状态的操作数量。

核心判断

若只留下第 \( i \) 个位置,则必须有

\[b_i=S\oplus a_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;
}
posted @ 2026-07-18 19:45  艾拉别哭  阅读(12)  评论(0)    收藏  举报