2018牛客网暑假ACM多校训练赛(第八场)H Playing games 博弈 FWT

原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round8-H.html

题目传送门 - https://www.nowcoder.com/acm/contest/146/H

题意

  有 $n$ 堆石子,第 $i$ 堆有 $a_i$ 个。请你取出尽量多堆石子,使得取石子nim游戏先手必胜。输出你选择的石子堆数。

  $n,a_i\leq 5\times 10^5$

题解

  首先我们把题转化成:在 $n$ 个数中选择尽量多的数,使得他们的异或值为 $0$ 。

  然后我们把题转化成:在 $n$ 个数中选择尽量少的数,使得他们的异或值为一个特定值 $C$ 。其中 $C=a_1\ {\rm XOR} \cdots {\rm XOR} a_n$ 。显然,答案为 $n-$ 你选择的数的个数。

  考虑将 $a_i$ 二进制的每一维拆开,看作一个 $d$ 维向量。其中由于 $a_i\leq 2^19$,所以我们取 $d=19$ 。

  由于 $d$ 维线性无关向量组最多有 $n$ 个向量,所以我们最多在那些数字里面取 $d$ 个。

  我们考虑二分答案,假设选择小于等于 $k$ 个就可以组成 $0$ 了。

  我们如何验证:

  对于原数组每一个数出现的位置都记一下,然后把位置 $0$ 也搞一下,然后取它在异或卷积意义下的 $k$ 次幂,判断一下可不可以合成 $C$ 即可。这个用 FWT 做。

代码

#include <bits/stdc++.h>
using namespace std;
int read(){
	int x=0;
	char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+ch-48,ch=getchar();
	return x;
}
const int N=1<<19,mod=1e9+7,inv2=5e8+4;
int n,a[N],b[N],v=0;
int Pow(int x,int y){
	int ans=1;
	for (;y;y>>=1,x=1LL*x*x%mod)
		if (y&1)
			ans=1LL*ans*x%mod;
	return ans;
}
void FWT(int a[],int n,int flag){
	for (int d=1;d<n;d<<=1)
		for (int i=0;i<n;i+=(d<<1))
			for (int j=0;j<d;j++){
				int x=a[i+j],y=a[i+j+d];
				a[i+j]=(x+y)%mod;
				a[i+j+d]=(x-y)%mod;
				if (flag==-1){
					a[i+j]=1LL*a[i+j]*inv2%mod;
					a[i+j+d]=1LL*a[i+j+d]*inv2%mod;
				}
			}
}
bool check(int x,int n){
	for (int i=0;i<n;i++)
		b[i]=Pow(a[i],x);
	FWT(b,n,-1);
	b[v]=(b[v]+mod)%mod;
	return b[v]>0;
}
int main(){
	n=read();
	memset(a,0,sizeof a);
	for (int i=1;i<=n;i++){
		int x=read();
		v^=x;
		a[x]++;
	}
	a[0]++;
	int m=1<<19;
	FWT(a,m,1);
	int L=0,R=19,mid,ans=19;
	while (L<=R){
		mid=(L+R)>>1;
		if (check(mid,m))
			R=mid-1,ans=mid;
		else
			L=mid+1;
	}
	printf("%d\n",n-ans);
	return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round8-H.html