【BZOJ4347】[POI2016]Nim z utrudnieniem
Description
A和B两个人玩游戏,一共有m颗石子,A把它们分成了n堆,每堆石子数分别为a[1],a[2],...,a[n],每轮可以选择一堆石子,取掉任意颗石子,但不能不取。谁先不能操作,谁就输了。在游戏开始前,B可以扔掉若干堆石子,但是必须保证扔掉的堆数是d的倍数,且不能扔掉所有石子。A先手,请问B有多少种扔的方式,使得B能够获胜。
Input
第一行包含两个正整数n,d(1<=n<=500000,1<=d<=10)。
第二行包含n个正整数a[1],a[2],...,a[n](1<=a[i]<=1000000)。
本题中m不直接给出,但是保证m<=10000000。
Output
输出一行一个整数,即方案数对10^9+7取模的结果。
Sample Input
5 2
1 3 4 1 2
1 3 4 1 2
Sample Output
2
题解:一开始没看到m是所有石子个数的和,GG。。。
Nim游戏结论(说n遍了):所有堆的石子数异或和不为0则先手必胜,否则先手必败。
这个有什么用?我们将所有堆从小到大排序,对于一个数x,它和比它小的数中任意一些数的异或和不会超过2*x,所以直接DP,用f[i][j]表示当前异或和为i,选出石子的堆数%d=j的方案数,答案就是f[0][n%d](也可以是f[sum][0])
但是TLE啊,狂T不止啊,要了数据手动测了30个点都没问题啊,不就是std新开了一个数组temp,而我吧temp和f数组合到一起了吗~
还好有网上的黑科技,将f[i][j]换成f[j][i]就过了,从TLE变成14s。
顿时觉得二位数组的复杂度真的玄学,估计那天丧心病狂开它12个一维数组没准就rank1了?实在不行写循环展开?(orz wys!orz wys!orz wys! O(wys)!O(wys)!O(wys)!)
#include <cstdio>
#include <algorithm>
#define mod 1000000007
using namespace std;
int n,m,d,ans;
int v[500010],f[12][1<<20];
int rd()
{
int ret=0; char gc=getchar();
while(gc<'0'||gc>'9') gc=getchar();
while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar();
return ret;
}
int main()
{
n=rd(),d=rd();
int i,j,k,x;
for(i=1;i<=n;i++) v[i]=rd();
sort(v+1,v+n+1),f[0][0]=1;
for(i=m=1;i<=n;i++)
{
x=v[i];
while(m<=x) m<<=1;
for(k=0;k<m;k++) f[d][k]=f[d-1][k];
for(j=d-1;j;j--) for(k=0;k<m;k++)
{
f[j][k]+=f[j-1][k^x];
if(f[j][k]>=mod) f[j][k]-=mod;
}
for(k=0;k<m;k++)
{
f[0][k]+=f[d][k^x];
if(f[0][k]>=mod) f[0][k]-=mod;
}
}
ans=f[n%d][0];
if(n%d==0) ans--;
if(ans<0) ans+=mod;
printf("%d",ans);
return 0;
}