【bzoj4347】[POI2016]Nim z utrudnieniem dp

题目描述

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(sumlimits_{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
" , (f[0][sum] - (n % d == 0) + mod) % mod);
	return 0;
}

 

原文地址:https://www.cnblogs.com/GXZlegend/p/7055243.html