石子游戏(nim游戏+按位考虑)

题意

\(n\)堆石子,每次最多可以从一堆中取\(x\)个,问你\(x = 1 ... n\)时的答案。

解法

经典\(nim\)游戏,找规律知\(sg[i] = i \ mod \ (x+1)\)

于是便要快速求\(a[1]\ mod\ (x+1) \bigoplus ... a[n]\ mod\ (x+1)\)

考虑按位做,设\(y = x+1\),对于\(k \leq \frac{n}{y}\),求出\(x \in [ky,(k+1)y)\),求有多少\(x-ky\)\(2^j\)这一位。

预处理出\(f_{j,i}\)表示对于\(x>=i\),有多少\(x-i\)\(2^j\)这一位,有

\[f_{j,i} = f_{j,i+2^{j+1}} \ + \sum^{i+2^{j+1}-1}_{k = i+2^j} c_k \]

其中\(c_i\)表示\(i\)出现的个数。

考虑统计答案,答案一定是几个\(2^{j+1}\)长度的整块加上一个散块,对于整块,差分即可,对于散块,发现在\([0,2^j-1]\)之间的贡献是\(0\),在\([2^j,2^{j+1}-1]\)之间是\(1\),分\((k+1)y\)在左右半边讨论即可。

code

#include<bits/stdc++.h>
#define ll long long
#define N 500015
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n;i>=a;i--)
#define inf 0x3f3f3f3f
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define lowbit(i) ((i)&(-i))
#define VI vector<int>
#define all(x) x.begin(),x.end()
using namespace std;
int c[N],f[21][N];
int n;
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
 	scanf("%d",&n);
 	rep(i,1,n){
 		int u; scanf("%d",&u); c[u] ^= 1;
 	}
 	per(i,0,n) c[i] ^= c[i+1];
 	rep(j,0,18){ // f(j,i) 表示 对于所有x >= i ,x-i有j这一位的异或 是0/1
 		per(i,0,n){
 			if((i+(1<<j)) > n) f[j][i] = 0; //x-i 有j这一位 -> x-i > 2^j
 			else if((i+(1<<(j+1))) > n) f[j][i] = c[i+(1<<j)];
 			else f[j][i] = f[j][i+(1<<(j+1))]^c[i+(1<<(j+1))]^c[i+(1<<j)]; //
 		}
 	}
 	rep(i,2,n+1){
 		int top = n/i;//(mod i)
 		bool ff = 0;
 		rep(j,0,18){
 			if((1<<j) >= i) break;
 			bool now = 0;
 			rep(k,0,top){
 				int l = k*i; // [k*i,(k+1)*i)
 				int r = l+((((i-1)>>(j+1))+1)<<(j+1)); // l ~ i-1向上第一个2^(j+1)的倍数
 				now ^= f[j][l];
 				if(r <= n) now ^= f[j][r];
 				now ^= c[min(max(l+i,r-(1<<j)),n+1)]^c[min(r,n+1)];
 			}
 			if(now){ff = 1; break;}
 		}
 		if(ff) printf("Alice ");
 		else printf("Bob ");
 	}
	return 0;
}
原文地址:https://www.cnblogs.com/czdzx/p/14008017.html