洛谷 P5970 [POI2016]Nim z utrudnieniem
题意
\(\texttt{Nim}\)游戏,一共\(m\)颗石子分成了\(n\)堆,每堆石子数为\(a_i\),后手可以在游戏开始前扔掉\(d\)的倍数堆石子,但不能扔掉所有石子,求后手获胜方式有多少种。
\(d\leq10,n\leq5\times10^5,a_i\leq10^6\)\(m\)不直接给出但保证\(m\leq10^7\)
思路
\(\texttt{Nim}\)游戏后手必胜条件:每堆石子的个数异或和为\(0\)
设\(f[i][j][k]\)表示到第\(i\)个数,前\(i\)个数中选出的数的个数\(\text{mod}\ d=j\),异或和为\(k\)的方案数,每次枚举当前堆是否扔掉。
但是时间复杂度是\(O(nmd)\)的,显然不能过……
优化需要注意到异或具有的性质:任意个\(\leq x\)的数的异或和\(\leq2x\)。具体来讲就是说任意长度为\(k\)的数的异或和小于任意长度为\(k+1\)的数
因此可以考虑将所有堆的石子数从小到大排序,每次新加入一个数,数的上界一定不会大于\(2\times a_i\)
所以这样时间复杂度就变成了\(O(\sum\limits_{i=1}^na_id)=O(md)\)
但是空间超了,但是只有\(k\bigoplus 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 << '\n';
return 0;
}
转载不必联系作者,但请声明出处