基础结论:
巴什博弈:一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜
结论:
n
n%(m+1)==0
n先手必输
斐波那契博弈:有一堆个数为n(n>=2)的石子,先手不能在第一次把所有的石子取完,至少取1颗,之后每次可以取的石子数至少为1,至多为对手刚取的石子数的2倍,取走最后一个石子的人为获胜
结论:如果
n
n
n 是一个斐波那契数,那么先手必败
威佐夫博弈:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜
结论:
⌊
(
b
−
a
)
∗
(
5
+
1
)
2
⌋
=
a
\lfloor {(b-a)*(\sqrt {5}+1) \over 2} \rfloor =a
⌊2(b−a)∗(5+1)⌋=a先手必败
poj 1067
nim博弈:有
n
n
n 堆各若干个物品,两个人轮流从某一堆取任意多的物品,最后取光者得胜。
结论:
a
1
a_1
a1^ …^
a
n
=
0
a_n=0
an=0先手必败
洛谷 P2197
anti-nim:有
n
n
n 堆各若干个物品,两个人轮流从某一堆取任意多的物品,最后取光者输。
结论:
1.有个数大于1的堆,异或和不为0,先手必胜
2.没有个数大于1的堆,异或和为0,先手必胜
(
f
l
a
g
&
&
s
u
m
)
∥
(
!
f
l
a
g
&
&
!
s
u
m
)
(flag\&\&sum) \| (!flag\&\&!sum)
(flag&&sum)∥(!flag&&!sum)
阶梯博弈:有n个阶梯呈升序排列,每个阶梯上有若干个石子,可行的操作是将一个阶梯上的石子移任意个(>0)到前一个台阶。当没有可行操作时输
结论:看作奇数台阶的nim博弈
树上删边博弈:给定一棵有根树,A和B分别轮流删边,删边后不与根联通的子树也一并删去,最后不能删边就输了
结论:子树根节点
s
g
u
=
s
g
v
1
sg_u=sg_{v_1}
sgu=sgv1^ … ^
s
g
v
n
sg_{v_n}
sgvn
HDU - 1404
(1)先手必胜的状态一定能转移到某一个先手必败的状态
(2)先手必败的状态能转移到的状态一定是先手必胜
打表每个数字是必胜点还是必败点
特判开头为0的字符串:必胜
其它情况可以通过打表得到的数组直接判断
打表:0是必胜点
枚举
i
i
i:1-1000000,对于必败点,我们通过题意的操作回退到的点一定是必胜点
操作一:将
i
i
i的某一位数字增加
操作二:将
i
i
i后加一个0,0后随意加数字
HDU - 2873
二维sg函数
对于边界炸弹:
(
0
,
i
)
,
(
i
,
0
)
(0,i),(i,0)
(0,i),(i,0),可看做nim取石子,sg值都为
i
i
i
然后枚举
i
,
j
i,j
i,j打表,套getsg板子,要注意的是操作一次,炸弹会变成两个,相较于之前要么行坐标改变,要么列坐标改变,因此枚举操作
i
,
j
i,j
i,j点后的两个点,求两个sg的异或和,再枚举得出mex确定
s
g
i
,
j
sg_{i,j}
sgi,j
HDU - 1809
将二维数组转化为字符串,然后sg暴力打表
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
std::map<std::string,int> sg;
std::map<std::string,bool> vis;
int n,m;
int dfs(std::string s){
if (vis[s]){
return sg[s];
}
vis[s]=1;
std::vector<int> flag(25);
for (int i=0;i<s.length();i++){
if ((i+1)%m==0||i/m==n-1) continue;
if (s[i]=='0'&&s[i+1]=='0'&&s[i+m]=='0'&&s[i+1+m]=='0'){
std::string temp=s;
temp[i]=temp[i+1]=temp[i+m]=temp[i+m+1]='1';
int x=dfs(temp);
if (x<=20){
flag[x]=1;
}
}
}
while (flag[sg[s]]){
sg[s]++;
}
return sg[s];
}
void yrzr(){
int q,ans=0;
std::cin>>q;
while (q--){
std::cin>>n>>m;
std::string s;
for (int i=1;i<=n;i++){
std::string ss;
std::cin>>ss;
s+=ss;
}
ans^=dfs(s);
}
std::cout<<(ans?"Yes\n":"No\n");
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int T=1;
// std::cin>>T;
while (T--){
yrzr();
}
return 0;
}
HDU - 1524
图上跑sg,图不一定连通
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
void yrzr(){
int n;
std::cin>>n;
std::vector<std::vector<int>> e(n);
for (int i=0;i<n;i++){
int k;
std::cin>>k;
for (int j=1;j<=k;j++){
int x;
std::cin>>x;
e[i].push_back(x);
}
}
std::vector<int> sg(n+1),vis(n+1);
std::function<int(int)> dfs=[&](int u){
if (vis[u]){
return sg[u];
}
vis[u]=1;
std::vector<int> ok(n+2);
for (auto v:e[u]){
dfs(v);
ok[sg[v]]=1;
}
while (ok[sg[u]]){
sg[u]++;
}
return sg[u];
};
int m;
while (std::cin>>m){
if (!m){
return;
}
int ans=0;
for (int i=1;i<=m;i++){
int x;
std::cin>>x;
ans^=dfs(x);
}
if (ans){
std::cout<<"WIN\n";
}else{
std::cout<<"LOSE\n";
}
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int T=1;
// std::cin>>T;
while (T--){
yrzr();
}
return 0;
}
HDU - 3595
Every-SG板子题,暴力打表贪心求最长/最短游戏步数(待交HDU)
#include <iostream>
#define ll long long
#define ull unsigned long long
int n;
int stp[1005][1005],sg[1005][1005];
int dfs(int x,int y){
if (x<y){
std::swap(x,y);
}
if (x==0||y==0){
return 0;
}
if (stp[x][y]){
return sg[x][y];
}
int minn=1e9,maxn=0;
for (int i=y;i<=x;i+=y){
if (!dfs(x-i,y)){
maxn=std::max(maxn,stp[x-i][y]+1);
sg[x][y]=1;
}else{
minn=std::min(minn,stp[x-i][y]+1);
}
}
if (sg[x][y]){
stp[x][y]=maxn;
}else{
stp[x][y]=minn;
}
return sg[x][y];
}
void yrzr(){
int ans1=0,ans2=0;
for (int i=1;i<=n;i++){
int x,y;
std::cin>>x>>y;
if (x<y) std::swap(x,y);
if (dfs(x,y)){
ans1=std::max(ans1,stp[x][y]);
}else{
ans2=std::max(ans2,stp[x][y]);
}
}
if (ans1>ans2){
std::cout<<"MM\n";
}else{
std::cout<<"GG\n";
}
}
int main(){
while (std::cin>>n){
// if (!n) return 0;
yrzr();
}
return 0;
}
HDU - 1729
首先看到这种神奇的放石子方法,对于所有的
c
c
c,只要满足
c
+
c
∗
c
>
=
s
c+c*c>=s
c+c∗c>=s,就可以看作经典的nim取石子,
s
g
=
s
−
c
sg=s-c
sg=s−c。
然后考虑
c
+
c
∗
c
<
s
c+c*c<s
c+c∗c<s的情况,能不能找到一种比较显然的先手必输的情况呢,我们发现,当
c
c
c是满足
c
+
c
∗
c
<
s
c+c*c<s
c+c∗c<s的最大
c
c
c时,先手必败,因为无论怎么走,下一步一定会转移到
c
+
c
∗
c
>
=
s
c+c*c>=s
c+c∗c>=s的情况。
再考虑剩余
c
+
c
∗
c
<
s
c+c*c<s
c+c∗c<s的情况,假设满足条件的
t
=
m
a
x
(
c
)
t=max(c)
t=max(c),可以将
(
s
,
c
)
(s,c)
(s,c) 转换为
(
t
,
c
)
(t,c)
(t,c),因为
t
t
t 是一个
s
g
=
0
sg=0
sg=0的先手必败态,等效于
s
g
s
=
0
sg_s=0
sgs=0这个终止态
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
int dfs(int s,int c){
int x=sqrt(1.0*s);
while (x+x*x>=s){
x--;
}
if (c>x){
return s-c;
}
else if (c==x){
return 0;
}
return dfs(x,c);
}
void yrzr(){
int n,ans=0;
std::cin>>n;
for (int i=1;i<=n;i++){
int s,c;
std::cin>>s>>c;
ans^=dfs(s,c);
}
if (ans){
std::cout<<"Yes\n";
}else{
std::cout<<"No\n";
}
}
翻硬币游戏结论:当前局面sg值为正面朝上硬币单独出现的异或和
1011=(1) ^ (001) ^ (0001)
树的删边游戏: A tree game
结论某个子树的根节点的
s
g
u
sg_u
sgu 为各孩子
(
s
g
v
+
1
)
(sg_v+1)
(sgv+1) 的异或和
拓展:如果变成图存在简单环:偶数环可看作一个点,奇数环看作一个新点和一条边
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
void yrzr(){
int n;
std::cin>>n;
std::vector<std::vector<int>> e(n+1);
for (int i=1;i<n;i++){
int x,y;
std::cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
std::vector<int> sg(n+1);
std::function<void(int,int)> dfs=[&](int u,int fa){
for (auto v:e[u]){
if (v==fa) continue;
dfs(v,u);
sg[u]^=sg[v]+1;
}
};
dfs(1,0);
if (sg[1]){
std::cout<<"Alice\n";
}else{
std::cout<<"Bob\n";
}
}
CF 1498F
树上阶梯博弈
根据阶梯博弈的结论,只有
⌊
d
e
p
u
k
⌋
%
2
=
1
\lfloor {dep_u \over k} \rfloor \% 2=1
⌊kdepu⌋%2=1的节点会产生贡献,如果固定根,他们各点的异或和很好求,但是现在明显要用换根来计算各点作为根的贡献,首先我们考虑第一遍dfs的时候需要什么状态,比较显然的就是当
d
e
p
u
=
0
dep_u=0
depu=0时,
u
u
u为根子树内所有节点
x
x
x,(
⌊
d
e
p
x
k
⌋
%
2
=
0
/
1
\lfloor {dep_x \over k} \rfloor \% 2=0/1
⌊kdepx⌋%2=0/1) 状态
0
/
1
0/1
0/1 的异或和,然后还要一个状态表示
d
e
p
%
k
dep\%k
dep%k的值,转移就很好想了。
最后换根
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
constexpr int N=1e5+5;
std::vector<int> e[N];
int a[N],f[N][25][2],n,k,temp[25][2];
void dfs1(int u,int fa){
for (auto v:e[u]){
if (v==fa) continue;
dfs1(v,u);
}
f[u][0][0]=a[u];
for (auto v:e[u]){
if (v==fa) continue;
for (int i=0;i<k-1;i++){
for (int j=0;j<2;j++){
f[u][i+1][j]^=f[v][i][j];
}
}
f[u][0][1]^=f[v][k-1][0];
f[u][0][0]^=f[v][k-1][1];
}
}
void dfs2(int u,int fa){
if (fa){
for (int i=0;i<25;i++){
for (int j=0;j<2;j++){
temp[i][j]=f[fa][i][j];
}
}
for (int i=0;i<k-1;i++){
for (int j=0;j<2;j++){
temp[i+1][j]^=f[u][i][j];
}
}
temp[0][1]^=f[u][k-1][0];
temp[0][0]^=f[u][k-1][1];
for (int i=0;i<k-1;i++){
for (int j=0;j<2;j++){
f[u][i+1][j]^=temp[i][j];
}
}
f[u][0][1]^=temp[k-1][0];
f[u][0][0]^=temp[k-1][1];
}
for (auto v:e[u]){
if (v==fa) continue;
dfs2(v,u);
}
}
void yrzr(){
std::cin>>n>>k;
for (int i=1;i<n;i++){
int x,y;
std::cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
for (int i=1;i<=n;i++){
std::cin>>a[i];
}
dfs1(1,0);
dfs2(1,0);
for (int i=1;i<=n;i++){
int ans=0;
for (int j=0;j<k;j++){
ans^=f[i][j][1];
}
std::cout<<(ans>0)<<" ";
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int T=1;
// std::cin>>T;
while (T--){
yrzr();
}
return 0;
}