题意
( exttt{Nim})游戏,一共(m)颗石子分成了(n)堆,每堆石子数为(a_i),后手可以在游戏开始前扔掉(d)的倍数堆石子,但不能扔掉所有石子,求后手获胜方式有多少种。
(dleq10,nleq5 imes10^5,a_ileq10^6)(m)不直接给出但保证(mleq10^7)
思路
( exttt{Nim})游戏后手必胜条件:每堆石子的个数异或和为(0)
设(f[i][j][k])表示到第(i)个数,前(i)个数中选出的数的个数( ext{mod} d=j),异或和为(k)的方案数,每次枚举当前堆是否扔掉。
但是时间复杂度是(O(nmd))的,显然不能过……
优化需要注意到异或具有的性质:任意个(leq x)的数的异或和(leq2x)。具体来讲就是说任意长度为(k)的数的异或和小于任意长度为(k+1)的数
因此可以考虑将所有堆的石子数从小到大排序,每次新加入一个数,数的上界一定不会大于(2 imes a_i)
所以这样时间复杂度就变成了(O(sumlimits_{i=1}^na_id)=O(md))
但是空间超了,但是只有(kigoplus a_i)和(k)之间可以相互转移,所以可以开一个小空间(t),原地完成转移
还有就是(n)是(d)的倍数时会算上全选的情况,此时答案需要减(1)
常数很大,记得开(O2)
代码
/*
Author:Loceaner
DP+NIM游戏+状态优化
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int A = 5e5 + 11;
const int B = (1 << 20) + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
int n, d, sum, a[A], t[B], f[10][B], m = 1;
int main() {
n = read(), d = read();
for (int i = 1; i <= n; i++) a[i] = read(), sum ^= a[i];
sort(a + 1, a + 1 + n);
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
while (m <= a[i]) m <<= 1;
for (int j = 0; j < m; j++) t[j] = f[d - 1][j];
for (int k = d - 1; k; k--)
for (int j = 0; j < m; j++)
f[k][j] = (f[k][j] + f[k - 1][j ^ a[i]]) % mod;
for (int j = 0; j < m; j++) f[0][j] = (f[0][j] + t[j ^ a[i]]) % mod;
}
cout << (f[0][sum] - (n % d == 0) + mod) % mod << '
';
return 0;
}