概念
一.必胜态与必败态
- 必胜态(N),最优策略下谁面临此状态谁必赢。
- 必败态(P),最优策略下谁面临此状态谁必输。
二.SG函数
- mex运算:定义为求最小的不属于此集合的非负整数。
- SG(x)中x表示状态。x是必败态当且仅当SG(x) = 0;
- 令 S 为所有x走一步可以到达的状态(必须合法)。那么有$SG(x) = mex(SG(y))(y ∈ S) $
- 可以暴力求骗分,主要是打表找规律
三.SG定理
- 游戏和的 SG 函数就是所有子游戏的 SG 函数的异或和,其中所有子游戏互相独立。
- 所以先手必胜当且仅当每堆石子的 SG 函数的异或和不为 0。
例题
取火柴游戏
分析
- 由SG定理判断初始状态,设SG = a1a2a3...an.如果SG == 0就lose
- 如果必胜,假设是a1中某些被取走,就令a1变成a'。有a'a2a3...an = 0 = SG ^ SG = SGa1a2a3...^an;
- 所以SG^a1 = a'.且必有a1 > a';
- 所以一旦有SG^ai < ai,就是在第i堆取走ai - SG^ai个。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define lson x<<1
#define rson x<<1|1
#define ll long long
#define rint register int
#define mid ((L + R) >> 1)
using namespace std;
template <typename xxx> inline void read(xxx &x) {
char c = getchar(),f = 1;x = 0;
for(;c ^ '-' && !isdigit(c);c = getchar());
if(c == '-') c = getchar(),f = -1;
for(;isdigit(c);c = getchar()) x = (x<<1) + (x<<3) + (c ^ '0');
x *= f;
}
template<typename xxx>void print(xxx x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
const int maxn = 500010;
const int inf = 0x7fffffff;
const int mod = 1e9 + 7;
int SG;
int n,a[maxn];
int main()
{
read(n);
for(rint i = 1;i <= n; ++i) read(a[i]),SG ^= a[i];
if(!SG) {//当前即是必败态
printf("lose
");
return 0;
}
for(rint i = 1;i <= n; ++i) {
if((SG ^ a[i]) < a[i]) {
printf("%d %d
",a[i] - (SG ^ a[i]),i);
for(rint j = 1;j <= n; ++j) {
if(i ^ j) print(a[j]);
else print(a[i] ^ SG);
putchar(' ');
}
return 0;
}
}
return 0;
}
G - Game of Cards
分析
暴力求解SG函数
#include<iostream>
#include<cstdio>
#include<cmath>
#include<bitset>
#include<cstring>
#include<algorithm>
#define lson x<<1
#define rson x<<1|1
#define ll long long
#define rint register int
#define mid ((L + R) >> 1)
using namespace std;
template <typename xxx> inline void read(xxx &x) {
char c = getchar(),f = 1;x = 0;
for(;c ^ '-' && !isdigit(c);c = getchar());
if(c == '-') c = getchar(),f = -1;
for(;isdigit(c);c = getchar()) x = (x<<1) + (x<<3) + (c ^ '0');
x *= f;
}
template<typename xxx>void print(xxx x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
const int maxn = 1010;
const int inf = 0x7fffffff;
const int mod = 1e9 + 7;
int n,m,k;
int a[maxn];
int SG[maxn];
bitset<maxn>tem;
inline int solve() {
for(rint i = 1;i <= n; ++i) {//枚举SG[x]的x
SG[i] = 0;//初始化
tem.reset();
for(rint j = 0;j <= k && j < i; ++j) {//枚举可以转移的先前状态
if(i - j - a[i - j] >= 0) {
tem[SG[i - j - a[i - j]]] = 1;
}
}
for(rint j = 0;; ++j) {
if(!tem[j]) {//找最小非负整数
SG[i] = j;
break;
}
}
}
return SG[n];
}
int main()
{
int t;
read(t);read(k);
int ans = 0;
while(t--) {
read(n);
for(rint i = 1;i <= n; ++i) read(a[i]);
ans ^= solve();
}
if(ans) printf("Alice can win.");
else printf("Bob will win.");
return 0;
}
HDU-1730
分析
观察发现距离为0时SG[0] = 0,可以考虑以距离做nim游戏;
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define lson x<<1
#define rson x<<1|1
#define ll long long
#define rint register int
#define mid ((L + R) >> 1)
using namespace std;
template <typename xxx> inline void read(xxx &x) {
char c = getchar(),f = 1;x = 0;
for(;c ^ '-' && !isdigit(c);c = getchar());
if(c == '-') c = getchar(),f = -1;
for(;isdigit(c);c = getchar()) x = (x<<1) + (x<<3) + (c ^ '0');
x *= f;
}
template<typename xxx>void print(xxx x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
const int maxn = 100010;
const int inf = 0x7fffffff;
const int mod = 1e9 + 7;
int n,m;
int ti,ji;
int main()
{
while(~scanf("%d%d",&n,&m)) {
int ans = 0;
for(rint i = 1;i <= n; ++i) {
read(ti);
read(ji);
ans ^=(int)(abs(ti - ji) - 1);
}
if(ans) printf("I WIN!
");
else printf("BAD LUCK!
");
}
return 0;
}