JZOJ6020.【GDOI2019】模拟 石子游戏(Nim)

Description

在这里插入图片描述

1<=n,A=max(a[i])<=5e5,

Solution

  • Nim游戏的基本结论,当石子数异或和为0时先手必败,否则必胜。
  • 即要求最多的数异或和为0.
  • 反过来求最少的数异或和为所有的数的异或和。
  • 假定我们将所有的数丢入线性基,那么至少log个数就可以构成所有的情况,所以答案上界为20.
  • 接下来我们直接用fwt,每一次与自己相乘(多项式的乘法)。虽然可能会自己乘上自己,但是这对答案并没有影响,因为这个答案一定不如消去这两次的答案更优。
  • 具体来说设f[i]表示能否异或乘i(在自乘了若干次后)。最后查询的是f[s]是否为0。
  • 时间复杂度是Alog2A的。
  • 但是s是固定的,可以预先推算出其他位置对这个s位置的贡献的系数。
  • 有一个结论,位置t贡献系数为(-1)|s&t中1的个数|
  • 然后就可以预处理点值表达,每次O(n)自乘,O(n)求IFWT。
  • 注意取模(随便模一个质数)。
#include<cstdio>
#include<cmath>
#include<algorithm> 
#include<cstring>
#define maxn 500005
#define ll long long 
#define mo 998244353
using namespace std;

int n,i,j,k,a[maxn],s,lim,A,nm;
ll f[maxn*2],g[maxn*2],ct[maxn*2],sum;

void fwt(ll *a,int sig){
	for(int mid=1;mid<lim;mid<<=1)
		for(int j=0;j<lim;j+=mid<<1)
			for(int k=0;k<mid;k++) {
				ll x=a[j+k],y=a[j+k+mid];
				a[j+k]=(x+y)%mo;
				a[j+k+mid]=(x-y+mo)%mo;
			}
}

int main(){
	freopen("nim.in","r",stdin);
	freopen("nim.out","w",stdout);
	scanf("%d",&n);
	for(i=1;i<=n;i++) scanf("%d",&a[i]),s^=a[i],A=max(A,a[i]);
	A=max(A,s);
	for(lim=1,nm=0;lim<=A;lim<<=1,nm++);
	for(i=1;i<lim;i++) ct[i]=ct[i-(i&-i)]+1;
	
	if (s==0){printf("%d
",n);return 0;}
	
	for(i=1;i<=n;i++) g[a[i]]++;
	fwt(g,1);
 	memcpy(f,g,sizeof(f));
	for(i=1;i<=(int)log2(A);i++){
		sum=0;
		for(j=0;j<lim;j++) {
			sum=(sum+((ct[j&s]&1)?-1:1)*f[j]%mo+mo)%mo;
			f[j]=f[j]*g[j]%mo;
		}
		if (sum>0) {printf("%d
",n-i);break;}
	}
}
原文地址:https://www.cnblogs.com/DeepThinking/p/11700953.html