原文鏈接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$ 個就可以組成 $C$ 了。
我們如何驗證:
對於原數組每一個數出現的位置都記一下,然后把位置 $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;
}
