博弈论(Game Theory)

博弈论(Game Theory)

首先先说两个定义
N状态:前面的一个玩家必胜
P状态: 后面一个玩家必胜

巴什博弈 (Bush Game)

有一堆数量为n的物体,轮流拿,至少拿1个,至多拿k个(N>K);
如果n%(k+1)==0,那么先手必败。
这一切是显而易见,毫无疑问的

一道例题
Tang and Jiang are good friends. To decide whose treat it is for dinner, they are playing a game. Specifically, Tang and Jiang will alternatively write numbers (integers) on a white board. Tang writes first, then Jiang, then again Tang, etc... Moreover, assuming that the number written in the previous round is X, the next person who plays should write a number Y such that 1 <= Y - X <= k. The person who writes a number no smaller than N first will lose the game. Note that in the first round, Tang can write a number only within range [1, k] (both inclusive). You can assume that Tang and Jiang will always be playing optimally, as they are both very smart students.

这一道题目大意上和Bush Game 差不多,但是不同的是它里面说的是写不出不小于n的就输了,那么必胜的时候就是你已经写出了n-1的时候。所以此时如果(n-1)%(k+1)==0那么先手必定输,否则就是后手输,因为先手那一次后可已转换为第一种情况

尼姆博弈 (Nim Game)

有n堆物体,每一堆的数量为a[i]个,每一次一个人任选一堆取出任意个(不能为0)
设k为每一堆异或的结果,如果k==0,那么先手必定失败,否则先手必定胜利。
这样来想,把每一堆的数量转换成二进制然后竖着来最低位对齐。如果说k==0,那么这意味这每一个列的1的个数一定会是偶数个。而第一个人取走了一些以后呢,这意味着一定一些列的1的个数为变成奇数个,那么k一定就不再等于0了。而此时后手的人只要把异或的值修正为0就可以了。
因为k不等于0了,那么这就说明k的最高位一定会是1,而这个最高位的1一定会是a[i]中的对应位上的1提供的
而根据异或的性质,k和a[i]异或结果为其他的异或结果。
我们可以在a[i]中减去一些值,是a[i]最终等于k和a[i]异或的结果,又因为k最高位的1是由a[i]提供的,那么那里一定会变成0,就一定会比a[i]小,而只要把这里减掉,k就又会等于0了

SG函数

公平组合游戏

1.双方交替来进行;
2.游戏进行的任意时刻,可以执行的合法行动与哪一个玩家执行无关;
3.当玩家无法行动的时候,就判负

mex运算

\[mex(S)=min\{x|x\in N,x\notin S\} \]

SG函数

对于任意状态下的x

\[SG(x)=mex\{SG(y)|y是x的后继状态\} \]

对于终止状态,SG值为0。

如果某一状态后继SG有0,则当前状态为N
如果当前状态所有后继SG不为0,则当前为P;

好的,既然已经知道了SG函数是什么,那么就有一道题了。

移棋子游戏

Description

给定一个有N个节点的DAG图,图上某些节点上面有棋子。两名玩家交替移动棋子,玩家每一次可以将任意一颗棋子沿着有向边移动到下一个节点,当无法移动的时候,就输掉了游戏,假设双方都足够聪明,问先手必胜还是后手必胜。

Input

第一行三个整数N,M,K,表示N个节点M条边K个棋子。接下来M行,每行两个整数x,y,代表x节点到y节点的有向边再接下来K行,表示K个棋子所在的节点编号。

output

先手胜输出"win",否则输出"lose"

Sample Input

6 8 4
2 1
2 4
1 4
1 5
4 5
1 3
3 5
3 6
1 2 4 6

Sample Output

win

这应该是我第一次这么抄题吧。
DAG图,看到这个应该回想起拓扑排序,其次,因为没有后继的点SG值为0,而每一个点的SG值又是有它的后继决定的。如果它的后继没有弄完,那么就不可以算。这很像拓扑排序把入度为0的点push进队列。
因此不难想到这一题可以用类似于拓扑排序的方法来做。我们需要存两个图,正向的和反向的。正向的图用来寻找这一个点的后继获取这一个点的SG值,反向的用来找祖宗,修改祖宗的出度,并把出度为0的祖宗push进入队列
因为有拓扑排序的基础,所以这一道题可以比较容易的做出来了

#include<bits/stdc++.h>
#define maxn 2003
using namespace std;
queue<int>Q;
vector<int>G[maxn];//正着存图
vector<int>G_[maxn];//反着存图
int chess[maxn],SG[maxn];//记录棋子的位置 ,SG函数值 
bool vis[maxn*3];
int n,m,outdgr[maxn];//记录点,边,出度 
int k; 

void solve(){
	for(int i=1;i<=n;i++){
		if(!outdgr[i]){
			Q.push(i);
		}
	}
	
	while(!Q.empty()){	
		memset(vis,0,sizeof(vis));	
		int u=Q.front();Q.pop();
		vector<int>::iterator iter=G[u].begin();
		int maxsg=0;
		while(iter!=G[u].end()){
			maxsg=max(maxsg,SG[*iter]);
			vis[SG[*iter]]=true;
			iter++;
		}
		int j;
		for(j=0;j<=maxsg+1;j++)if(!vis[j])break;
		SG[u]=j;//得到这一个点的SG值 
		vector<int>::iterator iter_=G_[u].begin();
		while(iter_!=G_[u].end()){
			outdgr[*iter_]--;
			if(!outdgr[*iter_])Q.push(*iter_);
			iter_++;
		}
	}
	int ans=SG[chess[1]];
	for(int i=2;i<=k;i++)ans^=SG[chess[i]];
	if(!ans)printf("lose\n");
	else printf("win\n");
	return ; 
}

int main(){
	memset(outdgr,0,sizeof(outdgr));
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		G[u].push_back(v);
		G_[v].push_back(u); 
		outdgr[u]++;
	}
	for(int i=1;i<=k;i++){
		scanf("%d",&chess[i]);
	}
	solve();
	return 0;
} 

那么,That's all.

posted @ 2019-01-31 16:53  Perisino  阅读(1750)  评论(4编辑  收藏  举报