博弈论与sg函数入门
记录一点结论性的东西,推导见百度吧。
首先博弈的前提是双方“绝对理智”。
一般的胜负博弈游戏来说,有以下几点:(注意必胜必败是针对这回合操作的人)
所有终结状态为必败点(比如五子棋a下出5连珠后,轮到b走,而5连珠为终结状态,所以b败,叫做必败点)。
所有1步操作能进入必败点的点为必胜点(比如该a走时a有4连珠,a只要能走出5连珠就进入了(b的)必败点,此时a必胜)。
某点的所有操作都走向必胜点,则该点为必败点。(和上一条对称)
Hdu 1517 上述定理的应用 本质还是搜索(有O(1)的数学方法)
Hdu 1079 日期模拟+博弈搜索 考验代码功底..(也有O(1)的数学方法)
可以发现,博弈的本质还是搜索,由一个局面到下一个局面,形成一颗树(或图),树的叶子节点是最终局面。
也有一些最终结果不是胜负的博弈,以https://nanti.jisuanke.com/t/19975 “Rake It In”为例(icpc现场赛题目,比赛的时候突然领悟..):
给4*4的矩阵,a,b分别进行操作,每人每次可以选择一个2*2的矩阵把他们的和加到最终分数里并逆时针旋转选中的矩阵(2人共用1个分数)。但a,b的目标不同:分别是让最终分数最大(最小)。求k轮操作最终的分数。
从第一步开始想很难,贪心显然是不行的,因为涉及矩阵的旋转,会改变整个矩阵。
但倒着想,在最后1步(最后1步是b走),b一定会选总和最小的矩阵。a走倒数第二步时知道对于每个局势b将怎么走,所以a会走最化大最小矩阵的步。
这是一个递归的过程。绝对理智走出第一步时,就把所有情节遍历了一遍,知道了最终答案。(其实并没博弈...开局就决定了胜负)
所以做法是:先遍历所有状态的分数,再递归求解绝对理智的第一步是怎么走的,就有了最终答案。(卡常数比较厉害,只能dfs,还需要一点树的知识)
或者用ab剪枝优化博弈树..
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[4][4], k, sum;
short num[600005], ans[66666];
void fx(int x, int y)
{
int temp = a[x][y];
a[x][y] = a[x][y + 1], a[x][y + 1] = a[x + 1][y + 1];
a[x + 1][y + 1] = a[x + 1][y], a[x + 1][y] = temp;
}
void cofx(int x, int y)
{
int temp = a[x][y];
a[x][y] = a[x + 1][y], a[x + 1][y] = a[x + 1][y + 1];
a[x + 1][y + 1] = a[x][y + 1], a[x][y + 1] = temp;
}
void dfs(int tim, int x)
{
num[x] = sum;
int i, j;
/*下一步是常数优化,注释这样写更容易懂但会tle..大概效率差了1倍
if (tim == k)
return;
*/
if (tim == k - 1)
{
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
num[x * 9 + i * 3 + j + 1] =
sum + a[i][j] + a[i][j + 1] + a[i + 1][j] + a[i + 1][j + 1];
return;
}
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
{
fx(i, j);
sum += a[i][j] + a[i][j + 1] + a[i + 1][j] + a[i + 1][j + 1];
dfs(tim + 1, x * 9 + i * 3 + j + 1);
cofx(i, j);
sum -= a[i][j] + a[i][j + 1] + a[i + 1][j] + a[i + 1][j + 1];
}
}
int viia(int tim, int x)
{
if (tim == k)
return num[x];
if (ans[x] != -1)
return ans[x];
if ((tim & 1) == 0)
{
int te = 0;
for (int i = 1; i <= 9; i++)
te = max(te, viia(tim + 1, x * 9 + i));
return ans[x] = te;
}
else
{
int te = 999999;
for (int i = 1; i <= 9; i++)
te = min(te, viia(tim + 1, x * 9 + i));
return ans[x] = te;
}
}
int main()
{
int t, i, j;
scanf("%d", &t);
while (t--&&scanf("%d", &k))
{
sum = 0;
k <<= 1;
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
scanf("%d", a[i] + j);
memset(num, 0, sizeof(num));
memset(ans, -1, sizeof(ans));
dfs(0, 0);
printf("%d\n", viia(0, 0));
}
return 0;
}
博弈的搜索求解的本质是树的遍历,但博弈论(sg函数相关)是从数学角度快速求解特定类博弈问题(直接上最终结论,但墙裂推荐认真学一遍推导过程)。
针对这类问题,应用sg函数可以快速解决:
“给定模型给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。”
比如给定1堆石子, 2人轮流操作,每次操作可以将某堆石子取出b[i]个,2人绝对理智,求最终谁胜。
定义对于自然数集合的mex运算,mex(A)表示A中最小的未出现的自然数。
定义对于节点的sg函数,x的子节点的sg函数构成的集合为A,则sg(x)=mex(A)。(对于叶子结点x,A为空集,mex(x)=0)
则有:对于状态x,先手必败当且仅当sg(x)=0。
这样理论上可以解决所有上述类型问题,可如果有n堆石子(每一步可以在n个图中选一个图操作)当n很大是时候,节点也非常多,算sg函数基于递归,计算量仍然很大。
另一个结论是:n个有向图游戏构成的游戏其sg函数为所有子游戏sg函数的异或。
理论部分就这些,推导有点麻烦(再次墙裂推荐看一遍推导)。
以取石子https://nanti.jisuanke.com/t/25083为例:
3堆石子,每次可以取1,3,7,9个,求最终谁胜。
分解为3个1堆石子的游戏,预处理出x个石子时的sg(x),判断sg(x)^sg(y)^sg(z)是否为0即可。
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[] = { 1,3,7,9 }, SG[1005], S[1005];
void getSG(int n)
{
int i, j;
memset(SG, 0, sizeof(SG));
//因为SG[0]始终等于0,所以i从1开始
for (i = 1; i <= n; i++)
{
//每一次都要将上一状态 的 后继集合 重置
memset(S, 0, sizeof(S));
for (j = 0; f[j] <= i && j <= 3; j++)
S[SG[i - f[j]]] = 1; //将后继状态的SG函数值进行标记
for (j = 0;; j++)
if (!S[j])
{ //查询当前后继状态SG值中最小的非零值
SG[i] = j;
break;
}
}
}
int main()
{
int n, m, k;
getSG(1000);
while (~scanf("%d%d%d", &n, &m, &k))
puts(SG[n] ^ SG[m] ^ SG[k] ? "win" : "lose");
return 0;
}
对于不好打表(不是取石子这种)的模型,可以直接在图上dfs出每个节点的sg函数。
Hdu 1847 sg函数模板题目 可以发现步数是2的幂时sg(x)=0当且仅当x%3=0(所以直接判%3也行)
Hdu 1848 同上
Poj 2234 同上,可以发现对于一堆石子取任意数目时sg(x)=x。
Hdu 2509 注意胜负条件和取石子相反,先取完的输。用必胜点的方法可以发现规律。特判所有堆都是1的情况就好。
Hdu 1907 同上
博弈论与sg函数入门相关推荐
- 博弈论与SG函数(Nim游戏)
博弈论与SG函数(Nim游戏) 目录 博弈论与SG函数(Nim游戏) 游戏状态 状态图(SG图) Nim 游戏 Nim 和 SG函数 Grundy数字 组合博弈游戏 Grundy 游戏 例题 在本篇, ...
- 算法竞赛进阶指南0x3A 博弈论之SG函数
算法竞赛进阶指南0x3A 博弈论之SG函数
- 《算法竞赛进阶指南》数论篇(3)-组合计数,Lucas定理,Catalan数列,容斥原理,莫比乌斯反演,概率与数学期望,博弈论之SG函数
文章目录 组合计数 例题:Counting swaps Lucas定理 Cnm≡Cnmodpmmodp∗Cn/pm/p(modp)C_n^m\equiv C_{n\ mod\ p}^{m\ mod\ ...
- Cow Digit Game(博弈论:sg函数)
链接:https://ac.nowcoder.com/acm/contest/1071/G 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言6553 ...
- 博弈论与 sg 函数
博弈论 定义 必胜状态 为 先手必胜的状态 , 必败状态 为 先手必败的状态 . 通过推理,我们可以得出下面三条定理: 定理 1:没有后继状态的状态是必败状态. 定理 2:一个状态是必胜状态当且仅当存 ...
- 博弈论之SG函数(NIM博弈、反NIM博弈证明+例题)--POJ2311
目录 NIM博弈: 题目: 代码: 反NIM博弈: 题目: 代码: 公平组合游戏ICG: 有向图游戏: Mex运算: SG函数: 有向图游戏的和: 定理: 题目: 代码: 参考材料: NIM博弈: 内 ...
- SG函数入门HDU 1848
SG函数 sg[i]为0表示i节点先手必败. 首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数.例如mex{0,1,2,4}=3. ...
- hdu1847(博弈论:sg函数)
Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Ja ...
- 牛客小白赛7 B自杀游戏 (博弈论,SG函数)
链接:https://www.nowcoder.com/acm/contest/190/B 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言6553 ...
最新文章
- AngularJS $q 和 $q.all 单个数据源和多个数据源合并(promise的说明)
- Oracle 检查点队列和HASH Bucket
- 在ubuntu上使用SSH客户端
- 一个linux提权用的技巧
- 天翼云从业认证(1.1)服务器的分类、用途、特点、结构和组件
- matplotlib 雷达图2
- Intellij代码界面导入maven工程,且该maven工程不具备intellij配置文件
- ubuntu16.04下安装NS-2.35以及对simple例的理解
- Could not load TestContextBootstrapper [null]. Specify @BootstrapWith‘s ‘value‘ attribute...
- 线程Thread类的start()方法和run()方法
- 让美团、京东、搜狐都说好的数据仓库,牛在哪?
- java I O类大全_Java I/O —— File类
- 用CSS绘制实体三角形并说明原理
- java jtextpane_java – 从JTextPane获取原始文本
- 物联网导论知识部分梳理
- 小样本不符合正态_尿液样本HPV分型检测用于宫颈癌筛查的可能性
- Error response from daemon: conflict: unable to delete feb5d9fea6a5 (must be forced) - image is bein
- 微信发红包案例测试场景
- w ndows无法连接到无线网络,windows无法连接到无线网络,详细教您windows无法连接到无线网络怎么办...
- Android 恢复出厂设置流程分析
热门文章
- 将对方的名字注册成殡葬行业商标?不合法!!!
- 拼多多TOKEN安卓登录方法
- 英语学习方法--听力和口语提高
- 永磁同步电机三相等效电路图_三相永磁同步电动机的控制电路的制作方法
- 量化进阶——如何进行期权套利(一)
- PIE-Engine:房山区洪涝灾害风险评价
- 安卓导航车机root方法_飞歌ES1互联网智能车机:一触即达已经OUT了!现在是一说即达...
- 潜在通路分析软件QA/SCAT V2.0用户使用说明书(目录)
- 玩转黑莓8900,不信你不会。超级实用
- SFP GBIC XFP SFP+光模块的区别杂谈