反Nim游戏动态规划计数方法
反Nim游戏的获胜条件可总结为:
- 当所有石子堆均为1时,堆数为奇数则先手必败,否则必胜
- 其余情况,石子堆异或和为0则先手必败,否则必胜
定义状态数组dp[i][0/1]表示前i堆石子中,先手是否取得最后一堆的合法分割方案数。状态转移需枚举上一分段终点j:
dp[i][0]转移方程
若区间[j+1, i]Nim游戏先手必胜:
dp[i][0] += dp[j][1]
若区间[j+1, i]Nim游戏先手必败:
dp[i][0] += dp[j][0]
dp[i][1]转移方程
若区间[j+1, i]反Nim游戏先手必胜:
dp[i][1] += dp[j][1]
若区间[j+1, i]反Nim游戏先手必败:
dp[i][1] += dp[j][0]
朴素实现时间复杂度O(n²),代码示例:
#include <iostream>
using namespace std;
const int MAXN = 1e6+5, MOD = 998244353;
int arr[MAXN], dp[MAXN][2];
int main() {
int T;
cin >> T;
while(T--) {
int n;
cin >> n;
for(int i=1; i<=n; i++) {
cin >> arr[i];
arr[i] ^= arr[i-1];
dp[i][0] = dp[i][1] = 0;
}
dp[0][1] = 1;
for(int i=1; i<=n; i++) {
for(int j=0; j<i; j++) {
if(arr[i] != arr[j])
dp[i][0] = (dp[i][0] + dp[j][1]) % MOD;
else
dp[i][0] = (dp[i][0] + dp[j][0]) % MOD;
}
bool allOne = true;
for(int j=i; j>=1; j--) {
if(arr[j]^arr[j-1] != 1) allOne = false;
if(allOne) {
if((i-j+1) & 1)
dp[i][1] = (dp[i][1] + dp[j-1][0]) % MOD;
else
dp[i][1] = (dp[i][1] + dp[j-1][1]) % MOD;
} else {
if(arr[i] != arr[j-1])
dp[i][1] = (dp[i][1] + dp[j-1][1]) % MOD;
else
dp[i][1] = (dp[i][1] + dp[j-1][0]) % MOD;
}
}
}
cout << dp[n][0] << "\n";
}
return 0;
}
优化方案:使用哈希表记录异或前缀和,分离连续1序列处理:
#include <iostream>
#include <unordered_map>
using namespace std;
const int MAXN = 1e6+5, MOD = 998244353;
int arr[MAXN], dp[MAXN][2];
unordered_map<int, int> cnt0, cnt1, seg0, seg1;
int main() {
int T;
cin >> T;
while(T--) {
cnt0.clear(); cnt1.clear();
seg0.clear(); seg1.clear();
int n;
cin >> n;
for(int i=1; i<=n; i++) {
cin >> arr[i];
arr[i] ^= arr[i-1];
dp[i][0] = dp[i][1] = 0;
}
dp[0][1] = 1;
cnt1[arr[0]] = 1;
int total = 1, segTotal = 0;
int segStart = 0;
int segDp[2][2] = {{0,1},{0,0}};
for(int i=1; i<=n; i++) {
dp[i][0] = (total - cnt1[arr[i]] + cnt0[arr[i]]) % MOD;
if(arr[i]^arr[i-1] > 1) {
while(segStart < i) {
seg0[arr[segStart]] = (seg0[arr[segStart]] + dp[segStart][0]) % MOD;
seg1[arr[segStart]] = (seg1[arr[segStart]] + dp[segStart][1]) % MOD;
segTotal = (segTotal + dp[segStart][1]) % MOD;
segStart++;
}
memset(segDp, 0, sizeof(segDp));
}
dp[i][1] = (segTotal - seg1[arr[i]] + seg0[arr[i]]) % MOD;
dp[i][1] = (dp[i][1] + segDp[!(i&1)][0] + segDp[i&1][1]) % MOD;
cnt0[arr[i]] = (cnt0[arr[i]] + dp[i][0]) % MOD;
cnt1[arr[i]] = (cnt1[arr[i]] + dp[i][1]) % MOD;
total = (total + dp[i][1]) % MOD;
segDp[i&1][0] = (segDp[i&1][0] + dp[i][0]) % MOD;
segDp[i&1][1] = (segDp[i&1][1] + dp[i][1]) % MOD;
}
cout << dp[n][0] << "\n";
}
return 0;
}