一、心得体会
1.ACM博弈题,不会的时候觉得难于上青天,会的时候觉得没有比博弈更水的题了;
博弈题看到的第一眼觉得是难题,代码敲完顿觉水题。你可能花半个小时去找规律,然后仅花2分钟敲代码。
2.博弈是单人游戏,也可以说是自己跟自己玩,因为“双方都做出最优决策”这一点限制了,最后的结果不取决
于你是谁,不取决于你的智商,只取决于你面对的局面;
3.局面,这是博弈里面最最最重要的东西!!!(所谓SG也是指这一局面的SG),博弈是一种不公平的游戏
因为游戏开始的时候已经结束了,影响你胜负的就是你所面对的局面,因为双方采取最优策略
故而局面必然会以双方当前对自己最优的路径走下去,所以结局已经确定了
4.当你面对一个局面的时候如何做出最优的决策呢?你一定是走到了最后一步才确定了胜负,所以当前的局面
往往需要从最终的局面逆推而来(也就是从一个已知胜负的局面一步步推导其他的局面,有了这样的思想,SG
也就不那么难理解了)
5.关于SG:
入门了博弈的人都知道,博弈里面常常用到一个重要的概念 -- SG。但是SG是什么?你去百度的话会有非常专业的解答,
但是那些所谓的专业绝对让人看的头疼。这里说说我所理解的博弈里面的SG(仅限博弈)
挑程里是这样解释SG值的:
除 任意一步所能转移到的子局面的SG值以外的最小非负整数。
仔细体会一下这句话,你会发现,这里对SG值的定义是递归定义的!
当前局面的SG是什么呢?请先去找当前局面的子局面的SG值。
显然,递归是有一个边界的,SG是一种递归,那么它也是有边界的,
不难发现,它的边界是没有子局面的局面(也就无法再转移的局面)
什么样的局面没有子局面呢,也就是胜负已定的局面。在第4点说到,
当前局面的最优策略是从胜负已定的最终局面逆推来的,这里的SG其实也是
说了这些,那么SG到底是什么呢?
联想当年学习递归的一个例子:
f(n) = 1 , n = 1
f(n-1) +1 ,n > 1
这样一个函数是我们学习递归时的经典例子,你说这里的F到底是什么?其实它不过是一个函数而已。
SG也是一样,它只是一个函数而已,函数这个词翻译成英语再翻译成中文,就成了“功能、作用”
那么SG的作用是什么呢?
举一个最简单的例子:
有一堆石头数量为n,两个人轮流从石堆拿{a1,a2,a3,......,ak}个石头,先取完所有石头者胜。
根据前面说的,首先找胜负已定的局面,当n=0的时候,石头被拿完了,败态
那么sg[0] = 0表示面对0个石头的局面者败,然后根据sg的定义,我们可以求出其他局面的sg值
(为了使每种局面确保有可以转移的子局面,我们假设{a1,a2,a3......,ak}里面一定有1,例如假设没有1的话
,假设为{5,6,7}那么局面4没有可以转移的子局面,这样会出现平局的情况,我们后面再说平局)
这样可以求出所有局面的sg值,然后sg的作用出来了~
我们发现,若sg[x] = 0,那么x是败态,这其中很神奇,鶸也说不清楚,只说一下胜态败态的转移
(其实光理解的话可能还是不知道什么是SG,但是看了后面的题目就能理解了并知道怎么用SG找到游戏的胜态败态了)
6.胜态与败态:
之前说了,博弈里面,游戏开始的时候已经结束了,影响你胜负的就是你所面对的局面。
也就是说,这个局面觉得了你的胜负,我们称能让你走向胜利的局面称为胜态,也是必胜态,专业术语也叫P态(积极的英语单词怎么写?)
称让你走向失败的状态称为败态,也是必败态,专业也叫N态(消极的英语单词鶸也不会拼。。。)
有一个很显然的规律:
只要当前状态可以转移到的状态中有一个是败态,那么当前状态就是胜态。
如果当前状态可以转移到的所有状态都是胜态,那么当前状态就是败态。
这两句话互为逆否命题,一眼就看出是对的就不解释了。
可以胜态败态的角度去理解下SG。
7.Nim游戏:
关于这个Nim游戏,百度的话又是一大堆乱七八糟看不下去的东西,
它的最原始的版本大概是说有N堆石头,{a1,a2,a3......,an}表示每堆的数量,两个人轮流选一个石堆拿若干石头(不能不拿),
如果轮到某个人时所有的石子堆都已经被拿空了,则判负。
这个游戏有个非常完美的结论:
令 s = a1^a2^a3....^an(^符号表示异或运算)
若 s = 0,则此局面为败态,否则为胜态
对于上面的式子,我们不难发现,当你从一个石堆拿走一些石头(即改变一个ai),一定会发生胜态和败态的转变
胜态一定会转移成败态,败态也一定有策略转移成胜态
当这个结论与SG结合,神奇的事发生
我们发现sg异或和为0的状态也是败态,否则胜态。
另外,很多游戏都可以转变为Nim的形式,例如POJ 1704(挑程上有讲解)
8.关于平局:
我们发现,一个必胜态的获得,必然是因为它可以转移到一个败态,那么是不是说相比于平局我们更倾向于败态呢?
如果有更多的败态,理论上可以转移出更多的胜态,但是孩子别太天真了啊~
博弈将“对敌人的仁慈就是对自己的残忍”这句话发挥的淋漓尽致,当你选择败态的时候,对方却不会傻傻按照你的想法给你转移胜态的
该你输的时候你还是得输,所以,在博弈里的决策,一定要是对自己最有利对对手最不利的策略才是最优策略,、
也就是说,如果实在不能赢,你一定宁可平局,也不要选择败态。例如今年HDU 多校题5754 里面马的情况
9.当初关于博弈看了很多但是都只是似懂非懂,只有做多了题才有更多的·体会
二、博弈做题技巧
做了个专题:点击打开博弈专题
题目其实好多都是做过的原题,不过以前都是自己找规律的,这次就是用SG打表找规律,通过这些题目也算是知道怎么使用SG找规律了
其中的题目大多都是打表找规律,不过也有一些有趣的题目
PS:题目选自kuangbin 的博弈分类:点击打开链接(难度的话,后面的题都蛮简单,前面的题稍难)
1.打表找规律题:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
/*
败态:
10 - 18
163 - 324
2917 - 5832
综上:
败态:
(9*18^i,18*18^i]
i从0开始
*/
const int N = 100000;
int sg[N+4];
void solve()
{
sg[1] = 0;
for (int i = 2;i <= N;++i)
{
set<int>s;
for (int j = 2;j <= 9;++j)
{
int to = i/j;
if (i%j)to++;
s.insert(sg[to]);
}
int g = 0;
while (s.count(g)) ++g;
sg[i] = g;
}
for (int i = 2000;i <= 9000;++i)
{
cout<<i<<" "<<sg[i]<<endl;
}
}
ll l[10],r[10];
void init()
{
l[0] = 9,r[0] = 18;
for (int i = 1;i <= 9;++i)
{
l[i] = 18LL*l[i-1];
r[i] = 18LL*r[i-1];
}
}
bool loser(ll x)
{
for (int i = 0;