博弈論(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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM