P5970 Nim z utrudnieniem

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;
}

posted @ 2020-07-06 22:28  pyyyyyy  阅读(139)  评论(0编辑  收藏  举报