博弈论
或许,这篇笔记能够让你成为替身使者,你渴望力量吗?
众所周知,达比(JOJO的奇妙冒险第三部中一反派)是一名狂热的赌徒,他曾经依靠赌博和替身能力赢得了很多人的财产和灵魂,并把这些灵魂做成筹码收集起来。他的快乐来自于赌博,和收集灵魂。他甚至对承太郎等人直言说,自己并不是受到了迪奥的命令,而是作为一名赌徒,想要赌博而赌博。他的赌博技巧极其高明,就连承太郎也评价他是一个极其危险的对手,差点使四人全灭。开始赌博时,会有口头禅“GOOD!”
综上所述,达比(或者是他的替身阿图姆神(アトゥム神))一定是学了博弈论,才会如此强大。
"博弈论,快用你那无敌的达比想想办法吧!!!"
博弈论
巴什博弈 Bash Game
Problem
有一堆物品共n个,两个人轮流拿,每次至少拿一个,至多拿k个,问是否有必胜的可能?先手必胜还是后手必胜?
结论
若n%(k+1)==0,,则先手必败
例题:CQYZ 取石子游戏1
Problem
Code
#include <cstdio>
#include <iostream>
using namespace std;
void read(int &n){
int num=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=num*10+ch-'0';
ch=getchar();
}
n=num*w;
}
int n,k;
int main(){
read(n);
printf(n%(1+k)==0?"2":"1");
return 0;
}
尼姆博弈 Nim Game
什么,是inm???
Problem
有n堆物品,第i堆数量为a[i],两个人轮流从某一堆取任意多的物品,每次至少一个,多者不限,最后取光者得胜。
结论
记k=a[1] xor a[2] xor a[3] xor ... xor a[n]
若k==0,则先手必败,否则先手必胜。
例题:luogu P2197 【模板】nim游戏
什么?是inm游戏?
Problem
Code
#include <cstdio>
#include <iostream>
using namespace std;
void read(int &n){
int num=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=num*10+ch-'0';
ch=getchar();
}
n=num*w;
}
const int maxn=10005;
int t,n,a[maxn],k;
int main(){
read(t);
while(t--){
read(n);
for(int i=1;i<=n;i++) read(a[i]);
k=a[1];
for(int i=2;i<=n;i++) k=k^a[i];
printf(k==0?"No\n":"Yes\n");
}
return 0;
}
SG函数
公平组合游戏
定义:
1、由对阵双方交替行动
2、游戏进程的任意时刻,可以执行的合法行动与轮到哪个玩家无关
3、不能行动的玩家判负。
有向图游戏
给定一个DAG图(有向无环),图中有唯一的起点,在起点处放一个棋子,两名玩家交替沿着边的方向移动棋子,每次只能移动一步,无法移动着判负。这样的游戏叫做“有向图游戏”。
显然,任何的公平组合游戏都可以转化为有向图游戏:我们把每个局面看作节点,当一个局面通过合法行动变成另一个局面时,给这两个局面节点连一条有向边。就像我们画状态转移图一样。
Mex运算
设S表示一个非负整数集合,定义Mex(S)表示求一个不属于S的最小非负整数的运算,即:
SG函数
在有向图游戏中,对于每个节点x,若其儿子为y1,y2…yk,定义函数SG(x),其值为x的所有儿子的SG函数值构成的集合再执行mex运算的结果,即:
特别的,整个有向图G的SG函数值被定义为起点s的SG函数值,即:SG(G)=SG(s)。令,对于终点e有SG(e)=0。
SG函数结论:
有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0.
有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0.
应用
有向图游戏的和
定义一个有向图游戏G,它的每一次行动都可以在G1,G2…Gm共m个子游戏中选择,这里的G就叫做有向图G1,G2…Gm的和。则
例题:CQYZ 移棋子游戏
Problem
分析
题目已经给出是DAG图,然后有K个棋子,自动联想到DAG游戏的和。
分析得到SG(u)的过程,我们需要得到这个u的所有后继节点vi的SG值。
所以考虑用拓扑排序。
于是便有两个部分:
1、求SG->需要找到后继->用正图存
2、求topolsit->需要找前继->用反图存
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
void read(int &n){
int num=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=num*10+ch-'0';
ch=getchar();
}
n=num*w;
}
const int maxn=2005;
int n,m,k,pos[maxn],rd[maxn],SG[maxn],vis[maxn];
vector<int> g[maxn],g2[maxn];//正图和反图 正图找后继求SG值 反图找祖宗拓扑排序
void toposort(){
queue<int> q;
for(int i=1;i<=n;i++) if(rd[i]==0) q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
//求Mex值
memset(vis,0,sizeof(vis));
int maxx=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
maxx=max(maxx,SG[v]);
vis[SG[v]]=1;
}
for(int mex=0;mex<=maxx+1;mex++)
if(!vis[mex]){
SG[u]=mex;
break;
}
//toposort
for(int i=0;i<g2[u].size();i++){
int v=g2[u][i];
if(rd[v]) rd[v]--;
if(rd[v]==0) q.push(v);
}
}
}
bool iswin(){
int S=SG[pos[1]];
for(int i=2;i<=k;i++) S=S^SG[pos[i]];
return S;
}
int main(){
read(n);read(m);read(k);
for(int i=1;i<=m;i++){
int u,v;read(u);read(v);
g[u].push_back(v);
g2[v].push_back(u);
rd[u]++;
}
for(int i=1;i<=k;i++) read(pos[i]);
toposort();
printf(iswin()?"win":"lose");
return 0;
}