luogu5675 [GZOI2017]取石子游戏

题目链接

solution

我们知道,一个局面当且仅当每堆石子数量的异或和为\(0\)时后手必胜。那么我们如果给\(Alice\)指定第一次选的堆,那么当他选完一次之后就成了后手。所以他肯定会尽量的使剩下的石子数量异或和为\(0\)

现在假设我们强制他选第\(i\)堆,且第\(i\)堆有\(x\)个石子。那么他可以通过选一次使得这堆石子的数量位于\([0,x-1]\)。所以我们其他石子数量的异或和不能位于\([0,x-1]\)这个区间。那么用\(f[i][j]\)表示前\(i\)堆石子异或和为\(j\)的方案数。\(g[i][j]\)表示\([i,n]\)堆石子,异或和为\(j\)的方案数。然后枚举一下强制选的位置。合并这两个\(dp\)数组就行了。

code

/*
* @Author: wxyww
* @Date:   2020-04-28 07:27:43
* @Last Modified time: 2020-04-28 07:35:08
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 310,mod = 1e9 + 7;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}

int f[N][N],g[N][N],a[N];

int main() {
	int n = read();

	for(int i = 1;i <= n;++i) a[i] = read();

	f[0][0] = 1;g[n + 1][0] = 1;

	for(int i = 1;i <= n;++i) {
		for(int j = 0;j < (1 << 8);++j) {
			f[i][j] += (f[i - 1][j ^ a[i]] + f[i - 1][j]) % mod;
			f[i][j] >= mod ? f[i][j] -= mod : 0;
		}
	}

	for(int i = n;i >= 1;--i) {
		for(int j = 0;j < (1 << 8);++j) {
			g[i][j] += (g[i + 1][j ^ a[i]] + g[i + 1][j]) % mod;
			g[i][j] >= mod ? g[i][j] -= mod : 0;
		}
	}

	ll ans = 0;

	for(int i = 1;i <= n;++i) {
		for(int j = a[i];j < (1 << 8);++j) {
			for(int k = 0;k < (1 << 8);++k) {
				ans += 1ll * f[i - 1][k] * g[i + 1][j ^ k] % mod;
				ans >= mod ? ans -= mod : 0;
			}
		}
	}

	cout<<ans;

	return 0;
}
posted @ 2020-04-28 07:45  wxyww  阅读(146)  评论(0编辑  收藏  举报