【备战蓝桥杯】6.异或数列——异或与乱猜

656 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述

Alice 和 Bob 正在玩一个异或数列的游戏。初始时,Alice 和 Bob 分别有一个整数 a 和 b,有一个给定的长度为 n 的公共数列 X1, X2, · · · , Xn。
Alice 和 Bob 轮流操作,Alice 先手,每步可以在以下两种选项中选一种:
选项 1:从数列中选一个 Xi 给 Alice 的数异或上,或者说令 a 变为 a ⊕ Xi。(其中 ⊕ 表示按位异或)
选项 2:从数列中选一个 Xi 给 Bob 的数异或上,或者说令 b 变为 b ⊕ Xi。
每个数 Xi 都只能用一次,当所有 Xi 均被使用后(n 轮后)游戏结束。游戏结束时,拥有的数比较大的一方获胜,如果双方数值相同,即为平手。
现在双方都足够聪明,都采用最优策略,请问谁能获胜?

输入

每个评测用例包含多组询问。询问之间彼此独立。
输入的第一行包含一个整数 T,表示询问数。
接下来 T 行每行包含一组询问。其中第 i 行的第一个整数 ni 表示数列长度,随后 ni 个整数 X1, X2, · · · , Xni 表示数列中的每个数。

输出

输出 T 行,依次对应每组询问的答案。
每行包含一个整数 1、0 或 1 分别表示 Alice 胜、平局或败。

样例输入

4
1 1
1 0
2 2 1
7 992438 1006399 781139 985280 4729 872779 563580

样例输出

1
0
1
1

提示

【评测用例规模与约定】
对于所有评测用例,1 ≤ T ≤ 200000,1 ≤ ∑Ti=1 ni ≤ 200000,0 ≤ Xi < 220。

思路

这道题看用例规模就知道不可能通过动态规划或者搜索之类的硬方法得分,必须从异或的特征入手。

众所周知,参与按位异或的所有数字中,任意一位若有偶数个1,则该位结果是0,奇数则为1。怎么利用这一点呢?

由于要比较两数字的大小,我们也许可以从高位到地位逐一比较,若其中有一位两者不一样,那就是该位为1的人获胜了。这正好同时利用了异或位运算和比较的特征。

我们分类讨论:

对于任意一位,

  • 当1的数量为偶数时

    由于偶数的可能组合仅可能为偶数+偶数或者奇数+奇数,那么无论怎么选双方这一位都肯定是相等的,要比低位才能知道大小。

  • 当1的数量为1时

    这个简单的情况下先手肯定直接赢。

  • 当1的数量大于1且为奇数时

    可能的组合只可能是奇数+偶数,凑到奇数个1的人会获胜。

    • 若数字有偶数个

      我们从小的开始模拟,发现有4、6个数字时,都是后手必赢,因此大胆假设所有偶数情况都是后手必赢。

    • 若数字有奇数个

      我们同理进行合理外推发现先手必赢。

    综上所述,代码就呼之欲出了。

    代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=200000+5;
int arr[maxn];
int num[21];
int main(){
	int t;
	cin>>t;
	while(t--){
		memset(num,0,sizeof(num));
		int n;
		cin>>n;
		for(int i=0;i<n;i++){
			cin>>arr[i];
			for(int j=0;j<20;j++){
				if(arr[i]&(1<<j))
					num[j]++;
			}
		}
		for(int i=19;i>=0;i--){
			if(num[i]%2==0){
				if(i==0){
					cout<<"0\n";
					break;
				}else{
					continue;
				}
			}else if(num[i]==1){
				cout<<"1\n";
				break;
			}else{
				if(n%2==0)cout<<"-1\n";
				else{
					cout<<"1\n";
				}
				break;
			}
		} 
	}
	return 0;
}

总结

像这种出现在倒数第一第二道的题,随便想想看看有没有巧法做就行了,别花太多时间在这些题上面,真想到怎么做算命好,想不到也认栽。