P5970 Nim z utrudnieniem
\(A\)和\(B\)在进行\(Nim\)博弈,\(A\)是先手,但是\(B\)在开始之前可以扔掉\(d\)的倍数堆的石子(不能扔掉全部的),问\(B\)有多少种扔的办法使\(B\)必胜。
显然\(B\)必胜的条件为\(a_1 \oplus a_2 \oplus \cdots a_n = 0\)(\(NIM\)博弈的结论)
显然可以这么来定义状态\(f[i][j][k]\)表示前\(i\)堆石子,扔掉了\(j\)堆(在\(mod\ d\)下),异或和是\(k\)的方案数
\(f[i][j][k]=f[i-1][j][k]+f[i-1][j-1][k\oplus a_i]\)
复杂度为\(O(n\cdot d\cdot maxa_i)\)
现在的问题在于怎么把\(maxa_i\)这一项变小?
有一个神奇的结论
因为对于任意一个正整数\(N\),它和比它自己小的数字异或起来的结果不会超过\(N \times 2\)
所以只需要把\(a_i\)从小到大排序,这样复杂度就变成\(O(n\cdot d)\)
现在问题在于空间开销太大了。
显然可以用滚动数组,但是仍然太大,因为我们必须要把\(i\)这一维给省掉。
只需要在每一层\(i\)时,按照\(k\)从大到小的顺序进行转移即可。
还要注意\(n\)是\(d\)的倍数时,会算上全选的情况,答案需要减去\(1\)
/*
@ author:pyyyyyy/guhl37
-----思路------
-----debug-------
*/
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500000 + 5;
const int mod = 1e9 + 7;
int sum, n, d, a[N], f[11][1 << 20], g[1 << 20];
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%d %d",&n, &d);
for(int i = 1; i <= n; ++i) scanf("%d",&a[i]),sum ^= a[i];
sort(a + 1, a + 1 + n);
f[0][0] = 1;
for(int i = 1; i <= n; ++i)
{
int max = 1;
while(max <= a[i]) max <<= 1;
for(int j = 0; j < max; ++j) g[j] = f[d - 1][j];
for(int k = d - 1; k ; --k)
for(int j = 0; j < max; ++j)
f[k][j] = (f[k][j] + f[k - 1][j ^ a[i]]) % mod;
for(int j = 0; j < max; ++j) f[0][j] = (f[0][j] + g[j ^ a[i]]) % mod;
}
if(n % d == 0) f[0][sum] -= 1;
cout << (f[0][sum] + mod)%mod;
return 0;
}
$$Life \quad is \quad fantastic!$$