题目描述
A和B两个人玩游戏,一共有m颗石子,A把它们分成了n堆,每堆石子数分别为a[1],a[2],...,a[n],每轮可以选择一堆石子,取掉任意颗石子,但不能不取。谁先不能操作,谁就输了。在游戏开始前,B可以扔掉若干堆石子,但是必须保证扔掉的堆数是d的倍数,且不能扔掉所有石子。A先手,请问B有多少种扔的方式,使得B能够获胜。
输入
第一行包含两个正整数n,d(1<=n<=500000,1<=d<=10)。
第二行包含n个正整数a[1],a[2],...,a[n](1<=a[i]<=1000000)。
本题中m不直接给出,但是保证m<=10000000。
输出
输出一行一个整数,即方案数对10^9+7取模的结果。
样例输入
5 2
1 3 4 1 2
样例输出
2
题解
dp
Nim游戏后手必胜条件:每堆的异或和为0
拿到这道题很容易想到是一道动态规划,并且能够想到状态f[i][j][k]表示前i个数中选出的数的个数mod d=j,使得异或和为k的方案数,初始化f[0][0]=1。
然而这样时间复杂度为$O(nmd)$,显然TLE。
我们可以考虑:如果将所有数从小到大排序,那么每次新加入一个数,数的上界的最高位一定不会大于当前数的最高位,一定不会大于2*a[i]。
所以这样可以把时间复杂度压到$O(\sum\limits_{i=1}^na_id)=O(md)$。
至于空间的问题,很容易想到使用滚动数组优化,然而还是会MLE。
其实并不需要滚动数组,由于k>0时用到的状态都是k-1,所以可以按照01背包的思路倒过来枚举k。当k=0时不成立,所以还要再开一个数组记录一下f[][d-1]的值.
常数较大。。。但有一个重要的点:二维数组一定要把较小的模数维放到第一维!
亲测把异或维放到第一维时间28s,把模数维放到第一维11s,大概是内部的内存分配问题。
最后的答案是f[0][sum],其中sum为所有数的异或和;另外由于不能拿走所有数,所以需要判断一下n%d是否为0并对应地减1。
#include <cstdio>
#include <algorithm>
#define N 500010
#define M (1 << 20) + 10
using namespace std;
const int mod = 1000000007;
int a[N] , f[10][M] , t[M];
int main()
{
int n , d , i , j , k , sum = 0 , m = 1;
scanf("%d%d" , &n , &d);
for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]) , sum ^= a[i];
sort(a + 1 , a + n + 1);
f[0][0] = 1;
for(i = 1 ; i <= n ; i ++ )
{
while(m <= a[i]) m <<= 1;
for(j = 0 ; j < m ; j ++ ) t[j] = f[d - 1][j];
for(k = d - 1 ; k ; k -- )
for(j = 0 ; j < m ; j ++ )
f[k][j] = (f[k][j] + f[k - 1][j ^ a[i]]) % mod;
for(j = 0 ; j < m ; j ++ ) f[0][j] = (f[0][j] + t[j ^ a[i]]) % mod;
}
printf("%d\n" , (f[0][sum] - (n % d == 0) + mod) % mod);
return 0;
}