P5675 [GZOI2017]取石子游戏 题解
2022/08/04,fix:第一堆石子应小于其他堆
Nim类石子游戏介绍
(学过的话可以跳到题目分析部分
\rm{Description}
给定数列
两人轮流从中取物品,规则:每次可以从一堆中拿走任意正整数个物品。先拿走最后一根的人胜利。
对于给定的数列,判断先手是否必胜,若必胜,输出第一次应该在哪堆取多少物品。
\rm{Solution}
必胜必败分析
先手必胜,当且仅当
对于其他 nim游戏 ,假设状态表示为
本题中
证明
设
也就是证明:
- 当
s \neq 0 时,存在一种取法,使得s=0 . - 当
s=0 时,无论怎么取物品,s 都不等于0 .
命题一
因为
此时,
所以命题一成立。
命题二
反证法。若否,则
设取走第
所以
把
所以命题二成立。
题目分析
显然,可以看出这个问题与Nim类游戏相关。Nim类游戏先手必胜,当且仅当每堆石子的数量的异或和不为
所以Alice先手必败仅有两种情况:
- 异或和为
0 ; - 异或和不为
0 ,但Alice选择的第一堆石子无法使异或和变成0.
第一种情况容易处理,对于第二种情况,可以发现:只有指定选的第一堆石子数量,小于其他堆的异或和时,才能够做到:删去一堆棋子,异或和也不为
所以就可以枚举Alice先选择哪一堆,然后每次DP处理:有多少种选择方法使得异或和等于
通过数学方法进行分析,容易得到上一段中的 DP 即可。
DP状态:
每次从上一行转移过来,具体看代码就行了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 208, MAXJ = 270, mod = 1e9 + 7;
int n;
int a[MAXN];
int dp[MAXN][MAXJ]; //dp[i][j]表示,当固定一堆不选时,其余的前i堆,异或和为j的方案数量
signed main() {
scanf("%lld", &n);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
int ans = 0; dp[0][0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
for(int k = 0; k < 256; k++) {
if(i == j) dp[j][k] = dp[j-1][k];
else dp[j][k] = (dp[j - 1][k] + dp[j - 1][k ^ a[j]]) % mod;
}
}
for(int j = a[i]; j < 256; j++) {
ans = (ans + dp[n][j]) % mod;
}
}
cout << ans << endl;
return 0;
}