博弈論(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運算
SG函數
對於任意狀態下的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.