博弈游戏结论
博弈论
前言
无证明,纯结论板子
类Chomp博弈
- 题意:本质上是先手开始时处在能选择后续游戏的先手与后手的状态上
- 胜负:只有一步操作时,先手按条件判胜负。否则先手必胜
Bash博弈
- 题意:一堆 \(n\) 个石子,可取出 \(x\sim y\) 个。拿走最后一个石子者获胜。
- 胜负: 仅当 $n \% (x+y)< x $ 时必败
- 当胜负条件取反时,仅当 \(n\% (x+y)<= x \land n\% (x+y) != 0\) 时必败
Fibonacci博弈
- 题意:一堆 \(n\) 个石子,先手者第一次可以取任意多个,但是不能取完,以后每次取的石子数不能超过上次取子数的 \(2\) 倍。取完者胜
- 胜负:当 \(n\) 是斐波那契数时,先手必败;否则先手必胜
Wythoff博弈
- 题意:两堆各若干石子,从任意一堆中至少取出一个或者从两堆中取出同样多的石子,规定每次至少取一个,至多不限,最后取光者胜。
- 胜负:设当前状态为 \((a,b)\) 且 \(a < b\),则当 \(a = \left\lfloor \frac{\sqrt{5} + 1}{2} (b - a) \right\rfloor\) 时,该状态为必败态。
const ld r=(sqrtl(5.0)+1.0)/2.0;
cout<<!(a==(int)(r*(b-a)))<<endl;
Nim游戏
- 题意:略
- 胜负:Nim 和为 \(0\) 时,先手必败
- SG函数:通过将每个子问题抽象为 \(SG\) 函数,并对 \(SG\) 值进行异或运算来判断胜负
- 一般DFS只在打表解决不了的情况下用,首选打表预处理。
anti-sg
-
题意:把终态败局改为终态胜局
-
胜负:先手必胜当且仅当:
- 游戏总 \(SG\) 不为 \(0\) 且某个子游戏的 \(SG\) 值大于 \(1\);
- 游戏总 \(SG\) 为 \(0\) 且不存在任何一个子游戏的 \(SG\) 值大于 \(1\)
multi-sg
- 题意:在符合拓扑原则的情况下,一个单一游戏的后续可以为多个子游戏
- 模型:有n堆石子,两个人可以从任意一堆石子中拿任意多个石子(不能不拿)或把一堆数量不少于2的石子分为两堆不为空的石子,无合法操作者输。
- 举例:\(SG(3)\)的后继状态有\({(0),(1),(2),(1,2)}\),他们的SG值分别为 \(\{0,1,2,{SG(1)\ xor\ SG(2) }\}\),因此 \(SG(3)=mex\{0,1,2,3\}=4\)
- 胜负:与普通 \(SG\) 类似,计算方面要想一想如何高效计算子游戏的\(SG\)的并(到头来还是硬算\(SG\))
every-sg
- 题意:给定一张无向图,上面有一些棋子,两个顶尖聪明的人在做游戏,每人每次必须将可以移动的棋子进行移动,不能移动的人输
- 理解:玩家目标实际上变成最后一个子游戏的胜利,那么必胜的子游戏尽可能拖时间,相反,必输的子游戏尽快结束。时间,就是距离游戏结束还有多少回合。必胜找剩余回合数最多的,必败找剩余回合数最少的。因此,用step记录“时间”得到
\(\text{step}(u) = \begin{cases} 0 & u \text{为终止状态} \\ \max \text{step}(v) + 1 & \text{sg}(u) > 0 \text{ 且 } v \text{为} u \text{的后续且 } \text{sg}(v) == 0 \\ \min \text{step}(v) + 1 & \text{sg}(u) == 0 \text{ 且 } v \text{为} u \text{的后续} \end{cases}\)
- 胜负:当所有子游戏中最大的 \(step\) 为奇数时,先手必胜
阶梯nim游戏
- 题意:每次可以从第 \(i\) 堆中取石子并将其移到第 \(i-1\) 堆,直到第 \(0\) 堆为止
- 胜负:若所有奇数位的石子数异或结果不为 \(0\),则先手必胜
题1 发现有点像阶梯博弈,但棋子的性质不好(能向左移动数位),而观察到移动棋子相当于将棋子左边的空格移动到棋子右侧。不妨将两棋子之间的空格数量当作阶梯nim的石头即可求解。
细节在于要排序后倒序异或,因为最后一个棋子的右侧是“取完了的区域”。
Nim-k
- 题意:最多可以从 \(k\) 堆中同时取石子
- 胜负:按二进制位统计1的数量,所有位的1的数量模 \(k==0\), 先手必败
树上删边游戏
- 题意:给定一棵以某点为根的树,每次删除一条边并将与根不相连的部分移除,无法操作者输
- 规律:
- 叶子节点的 \(SG\) 值为 0;
- 中间节点的 \(SG\) 值为其所有子节点 \(SG\) 值加 1 后的异或和
- 胜负:根节点 \(SG\) 值不为 0 时,先手必胜
- 变种:若树为有环树,通过Fusion定理 (奇偶是判断边的数量)将偶环缩掉,奇环缩成一个新点和新边,连到环上的边 连到 新点上变为树上删边
//无向有环图
vector<int> e[N];
int sg[N],S[N],top;
void dfs(int u,int fa){
S[++top] = u;
vis[u] = 1;
for(int x:e[u]){
if(x==fa)continue;
if(vis[x]==-1){
dfs(x,u);
if(vis[x])sg[u] ^= sg[x]+1;
}else if(vis[x]==1){
int tmp =S[top],cnt=1;
while(x!=tmp){
cnt++;
vis[tmp]=0;
tmp = S[--top];
}
if(cnt%2)SG[x]^=1;
}
}
}
void solve(){
int n;
cin>>n;
memset(vis,-1,sizeof(vis));
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
e[x].push_back(y),e[y].push_back(x);
}
dfs(1,0);
if(sg[1])cout<<"Alice"<<endl;
else cout<<"Bob"<<endl;
}
二分图博弈
最大匹配一定包含起点先手必胜
翻硬币游戏
题意
在游戏中,N 枚硬币排成一排,部分正面朝上,部分反面朝上。游戏开始时,从左端对硬币按 1 到 n 编号。游戏规则如下:玩家每次翻动硬币时,所翻硬币中最右侧的一枚必须由正面翻为反面。若无法执行操作(即无符合条件的硬币),则该玩家输局。
示例
假设仅允许翻动三枚硬币,则第三枚硬币必须是由正面翻为反面。若当前局面为“正正反”,则无法翻动硬币(因第三枚已是反面)。
一般规律
局面的 sg 值为其每个正面朝上硬币单一存在时 sg 值的异或和(XOR 和)。若异或和非零,则先手必胜;否则必败。
变种规则
以下是不同约束下的变种游戏,各变种中硬币编号从 1 起始(特例会说明):
-
每次只能翻一枚硬币
- 单一正面硬币 sg=1。
- 局 sg 值分析:正面硬币数为奇数时 sg=1(先手胜),偶数时 sg=0(先手败)。
-
每次能翻一枚或两枚硬币
- 等效于 Nim 游戏,sg[x]=x。
-
每次须连续翻 k 枚硬币
- 等效于巴什博弈,sg[x] 形式为周期序列:
000…01(其中0的数量为 k−1),循环出现。
- 等效于巴什博弈,sg[x] 形式为周期序列:
-
翻一枚硬币后,须翻动其左侧 k 枚中一枚(除非 x≤k)
- 等效于减法游戏,sg 值周期为 k+1:序列为
1, 2, 3, ..., k, 0, 1, 2, ..., k, 0, ...。
- 等效于减法游戏,sg 值周期为 k+1:序列为
-
每次须翻两枚硬币,且距离须在 S={1,2,3} 内(硬币序号从 0 起始)
- sg[x] 序列:
0, 1, 2, 3, 0, 1, 2, 3, 0, ...(周期为 4)。
- sg[x] 序列:
-
每次能翻一枚、二枚或三枚硬币(Mock Turtles 游戏)
- sg 序列:
1, 2, 4, 7, 8, 11, 13, 14, 16, 19, 21, 22, 25, 26, 28, ... - 相关概念:一个数在二进制中
1的个数为奇数时称odious,否则称evil。 - 规律:若 2x 是
odious,则 sg[x]=2x;若为evil,则 sg[x]=2x+1。 - 推广:每次可翻 1 至 k 枚硬币时,sg[1]=1,其余值取集合
mex{前 n 位至多 k 数字异或和}。
- sg 序列:
-
每次须翻连续任意枚硬币(至少一枚)(Ruler 游戏)
- sg[x]=lowbit(x),序列为
1, 2, 1, 4, 1, 2, 1, 8, ...。
- sg[x]=lowbit(x),序列为
-
每次须翻四枚对称硬币,且最左与最右必须由正面翻为反面(初始两端正面,Grunt 游戏)
-
等效于 Grundy 游戏变种:
- n=0,1,2 时为终止局面,sg=0。
- n≥3 时 sg 序列:
0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, ...。 - 规律:sg=max(0,(i−2)/2)
-
K倍动态减法
-
题意:有\(n\)个石子,两个游戏者轮流操作,第一个操作的人最多能拿走\(n-1\)个石子,以后,每个游戏者最多能拿走前一个游戏者拿走数目的\(k\)倍
-
胜负:
- \(k=1\),必败态是 \(2^i\)
- \(k=2\),Fibonacci博弈
- \(k>2\),类似Fibonacci博弈,把n分解成数列中不连续项的和,如果n就是数列中的数,必败
必胜时还要求输出第一步取法,按照上文的理解,将\(n\)分解之后,选择最小的一个\(a[i]\)即可(类似选择二进制的最小的\(1\))
构造的性质:我们用a数组表示要被求的数列,\(b\)数组中的\(b[i]\)保存 \(a[0...i]\) 组合能够构造的最大数字。那么要选用的下一项只能递减 寻找,直到找到\(a[t] * K < a[i]\) ,而\(b[t]\)就是\(a[0...t]\)所能构造的最大数字,再加上\(a[i]\), 即为\(a[0...i]\)能构造的最大数字,于是\(b[i] = b[t] + a[i]\)。
const int N = 1e6 + 5;
int k;
int flag = 0;
ll a[N], b[N];
void solve(){
ll k, s;
cis >> s >> k;
a[0] = b[0] = 1;
ist i = 0, j = 0;
while (s > a[i]) {
i++;
a[i] = b[i - 1] + 1; //首先求出当前的a数组
while (a[j + 1] * k < a[i]) j++;
if (a[j] * k < a[i])
b[i] = b[j] + a[i]; //然后根据a数组求b数组
else
b[i] = a[i];
}
if (s == a[i])
cout<<"Bob"<<endl;
else{
ll res = a[i];
while (s) {
if (s >= a[i]) s -= a[i];
res = a[i];
i--;
}
cout<<"Alice"<<' '<<res<<endl;
//res是Alice的第一步
}
}
高维nim和
一维:n 堆石子,轮流选一堆拿走任意非零个。
可以看作是\(x\)轴上撒 n 个黑点,其它都是白点,每次选一个黑点 \(a_i\),再选一个 \(x<a_i\),将线段 \(x∼a_i\)两端点颜色取反。
二维:平面上 n个黑点,每次选一个 $(a_i,b_i) $再选一个 \((x<a_i,y<b_i)\),将矩形 \((x,y)∼(ai,bi)\)四个顶点颜色取反。
三维:对应偏序上升到三维,将长方体八个顶点颜色取反。
以此类推,可以发现黑点之间是互不影响的,所以总状态就是黑点的 Nim和。
我们对于一些二维 Nim 游戏(更高维也行),可以拆分成两维单独的 Nim 然后求 Nim 积。
定义二维 Nim 游戏中的 Nim 积:\(x⊗y=mex{(a⊗b)⊕(a⊗y)⊕(x⊗b)∣a∈[0,x),b∈[0,y)}\)
其中 ⊗ 定义为 Nim 积,⊕ 定义为异或。
nim积可以和sg函数一样推广,即\(sg(x) = sg(x1)⊗ sg(x2)... 其中x1,x2...为x\)的后续子游戏
发现满足乘法交换律和乘法分配率, \(0\) 与所有数做$ Nim$ 积仍然为 $0 \(,\) 1$ 仍然是单位元。
定义费马数: \(2^{2^n},n\)为整数
- 费马数与任意小于它的数的 Nim 积为一般意义下乘法的积,即$ a⊗x=a×x (x<a)$
- 费马数与自己的 Nim 积为自己的 \(\frac 32\) 倍,即 \(a⊗a=3/2a=a⊕a/2 。\)
- 费马数内运算封闭(\(2^{32}内只需要处理a_i的32次乘积,并且预处理2的次方即可\))
//预处理
int nim(int x,int y,int p)
{
if(x*y<=min(x,y))return x*y;
int a=x>>p,b=((1ll<<p)-1)&x,c=y>>p,d=((1ll<<p)-1)&y,res=nim(b,d,p>>1);
return ((nim(a^b,c^d,p>>1)^res)<<p)^nim(nim(a,c,p>>1),(1ll<<p)>>1,p>>1)^res;
}
int p[35];
signed main()
{
for(int i=1,x=1;i<=32;i++,x<<=1)cout<<(p[i]=nim(x,x,32))<<'\n';
}
//自身nim积求值
inline int f(int x)
{
int res=0;
for(int i=0;i<=31;x>>=1,i++)res^=(p[i]*(x&1));
return res;
}
//二维
int res[1000][1000]=0;
ull f(ull x, ull y, int p = 32) {
if (x <= 1 || y <= 1) return x * y;
if (p < 8 && rem[x][y]) return rem[x][y];
ull a = x >> p, b = ((1ull << p) - 1) & x, c = y >> p, d = ((1ull << p) - 1) & y;
ull bd = f(b, d, p >> 1), ac = f(f(a, c, p >> 1), 1ull << p >> 1, p >> 1), ans;
ans = ((f(a ^ b, c ^ d, p >> 1) ^ bd) << p) ^ ac ^ bd;
if (p < 8) rem[x][y] = rem[y][x] = ans;
return ans;
}
小二维nim
- 题意:平面上\(n\)个黑点,每次选一个 \((a_i,b_i)\) 再选一个 \((x<a_i,y<b_i)\),将\((x,y)\),\((a_i,b_i)\)两个顶点颜色取反。
- 胜负:拆分成若干个子游戏,$SG(x,y)= x\ xor\ y $; 同普通一样的判断
取左右nim
只能取最左边和最右边的nim
首先设\(L[i][j]\)表示在\([i,j]\)这一段区间的左侧放上一堆数量为\(L[i][j]\)的石子后,先手必败。
同理定义\(R[i][j]\)表示右侧。
\(L(i,j)=\left\{\begin{array}{ll}0, & x=R(i,j-1), \\ x+1, & L(i,j-1) \leq x<R(i,j-1), \\ x-1, & R(i,j-1)<x \leq L(i,j-1), \\ x, & \text { otherwise. }\end{array}\right.\)
\(x\)表示\(a[j]\),区间dp,同理反向可以处理\(R(i,j)\),\(L(2,n)==a[1]\)先手必败
不平等博弈
没怎么看懂详细请见09年方展鹏大佬的论文
比较经典的问题是取黑白块,需要用sn函数解决
sn{左玩家到达的状态|右玩家到达的状态}
多个游戏的解求sn函数和
结论 x>0,先手必胜 ; x<0,后手必胜 ; x=0,后手必胜
double sn[20][20];
double get_sn(int x,int y){
if(sn[x][y]!=inf) return sn[x][y];
double l=-inf,r=inf;
for(int i=1;i<=x/2;i++) l=max(l,get_sn(i,y)+get_sn(x-i,y));
for(int i=1;i<=y/2;i++) r=min(r,get_sn(x,i)+get_sn(x,y-i));
if(l<0&&r>0) sn[x][y]=0;
else if(floor(l+1)>l&&floor(l+1)<r){
if(l>=0) sn[x][y]=floor(l+1);
else sn[x][y]=ceil(r-1);
}
else{
int j,k,tmp=1;
bool ok=0;
for(k=1;!ok;k++){
tmp<<=1;
for(j=floor(l*tmp+1);!ok&&j<tmp*r;j++) if(j>l*tmp&&j<r*tmp){
ok=1;
break;
}
}
sn[x][y]=1.0*j/tmp;
}
return sn[x][y];
}

浙公网安备 33010602011771号