[关闭]
@exut 2024-11-01T02:45:57.000000Z 字数 2385 阅读 159

从ICG到SG函数

博弈论 数学


SG函数是用于解决博弈论中公平组合游戏(ICG)问题的一种方法

ICG

这是啥?

定义大概就几条:

  1. 双方参与,轮流决策,决策最优
  2. 无法决策时游戏结束,无法决策者输,不论如何决策游戏都能在有限步完成
  3. 同一状态不可多次抵达,游戏无平局,任意决策者在决策点的行为与决策者无关仅与决策点有关

这就是 ICG,公平组合游戏

典型例子是 nim 游戏

必胜必败态

必败态:记为 P,直观理解就是到达这个状态的人已经必死无疑了
必胜态:记为 N,直观理解就是到这里就稳了,脑子不犯蠢就一定会赢

可以证明 ICG 里任意状态一定都是这两种状态,有策梅洛定理:

策梅洛定理:ICG 游戏中,任何一个局面先手或者后手其中之一必然存在必胜策略

如何判断一个状态是 NP 呢?

最直观的方法是:

DAG的博弈

有这样一个问题

给定一个有向无环图,在给定的起点处有一个棋子,两个炒鸡聪明的人交替移动这个棋子,不能移动者输

看起来是一个很呆的问题,但是这个问题单独拿出来的意义在于 任意一个 ICG 问题都可以归纳到这种形式(状态为点,状态间连边)

mex运算

马上要开始探究 SG 函数,但是在此之前......

首先我们需要定义一个奇怪的运算,mex

这是一个数集的运算符号, 代表的是不在集合 的最小自然数

例如

SG函数

表示从 ,存在直接的有向边,则

对于给定的 DAG,我们定义每个点的SG函数为:

但是只有这一个函数呢,他是啥用么有的

SG函数的性质

这个应该比较显然的说,因为此时后继状态都是空集

以上就是一维 函数的基本内容,利用这个例子我们可以解决一个简单的经典小问题

巴什博弈:一堆石子有 个,每次可以取 个石子。先取完者获胜。问何时先手必胜,何时后手必胜?

发现暴力算是可以解决的

如果追求效率,经常不会真的去暴力算 ,而是考虑直接归纳每个状态的 函数

如果算一遍 的代价可以承受,就可以考虑直接去算

但是如果不能归纳出 函数,这种博弈问题我们就是不能得出结论通解的

先介绍一下考场实用的一种方法:打表

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e5+5;
  4. int n,m;
  5. int SG[N];
  6. bitset<N> S,clr;
  7. void get_SG(){
  8. SG[0]=0;
  9. for(int i=1;i<=n;i++){
  10. S&=clr;
  11. for(int j=1;j<=min(m,i);j++){
  12. S[SG[i-j]]=1;
  13. }
  14. for(int j=0;;j++){
  15. if(!S[j]) {SG[i]=j;break;}
  16. }
  17. }
  18. }
  19. int main(){
  20. ios::sync_with_stdio(0);
  21. cin.tie(0),cout.tie(0);
  22. cin>>n>>m;
  23. get_SG();
  24. for(int i=1;i<=n;i++) cout<<SG[i]<<endl;
  25. }

根据定义,这份代码可以打出巴什博弈的 函数

我们可以观察出 在巴什博弈的规律是 ,这是我们观察出的结论

一般我们可以打出表来,然后再证明(当然不证也不是啥大事,只要你多看看确认是对的就行)

巴什博弈的证明是显然的,就不赘述了

SG定理

如果 只能解决一维的博弈问题,那这个玩意其实有点弱了

nim游戏

先用一个简单的例子来引入一下

nim game

这是一个可拆分的组合游戏

我们考虑一个子游戏,即考虑拿出任意一堆来做游戏

我们称 表示大小为 的堆的 函数,显然有

我们很容易得出面前的每一堆的子游戏是必胜还是必败

尝试用子问题的胜败推出总问题的胜败!

这里先考虑两个子问题的复合,因为多个子问题本质上就是两个的情况的扩展

等会,好像不太对劲

两个必胜在我面前时,我根本无法判断我必胜还是必败

有点悲哀,好像寄了,单个的 函数貌似无法直接复合生成,那难道没有办法了吗

SG定理

此时某位我不知道是谁的伟大的人给出了一个伟大的定理,SG定理

SG定理:游戏的“和”(其实是复合)的 值是各个子问题 异或和

然后我们可以发现这个问题我们解决了!

于是nim游戏的 已经非常简单的搞定了

代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e4+5;
  4. int n;
  5. int a[N];
  6. int T;
  7. void work(){
  8. cin>>n;
  9. int xr=0;
  10. for(int i=1;i<=n;i++)cin>>a[i],xr^=a[i];
  11. if(xr==0) cout<<"No\n";
  12. else cout<<"Yes\n";
  13. }
  14. int main(){
  15. cin>>T;
  16. while(T--){
  17. work();
  18. }
  19. }

短小精悍

证明不会

后面是例题环节

题目

XYD_OIFC2024 noip10.30T2

here(需要权限)

考察每个数的 ,容易得出 ,其余情况 ,显然 里面那一坨要么大于 要么是 所以 ,然后就做完了

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注