初學 博弈論 又稱對策論 Game Theory
博弈論 真的很有趣 ,回想起 前兩天多校一道題的題解 所有不公平的遊戲都存在必勝的玩法
與人鬥其樂無窮 https://vjudge.net/contest/241983#overview 博弈論專題
一)巴什博奕(Bash Game):有n個石頭,Alice和Bob輪流取石頭,每次取的石頭不能超過m個,Alice開始取,最後取完的贏,兩個人都是以最優的方案取,求最終贏的是誰
假設 n=m+1個,Alice 先取,無論她取多少個(>=1),Bob 都能一次性把剩下的取完,so ? Alice必輸
情況就可以推廣變成 如果 n= (m+1)*k 也就是 n%(m+1)==0 時,Alice 必輸 ,反之 Alice 必贏
代碼的話 實現很簡單 hdu2188 hdu2149
1 #include<cstdio> 2 int main(){ 3 int t,n,m; 4 scanf("%d",&t);while(t--){ 5 scanf("%d%d",&n,&m); 6 if(n%(m+1)==0)printf("Bob\n"); 7 else printf("Alice\n"); 8 } 9 return 0; 10 }View Code
二)威佐夫博奕(Wythoff Game):有兩堆石頭,Alice和Bob輪流從某一堆取任意多的石頭,或者從兩堆中取相同多的石頭,每次至少取一個石頭, Alice開始取,最後取完的贏,兩個人都是以最優的方案取,求最終贏的是誰(hdu 1846 hdu1527 51NOd1185)
就首先引入奇異局勢,也就是當面臨其一局勢的狀態必輸。 比如(0,0)就是一個奇異局勢,面臨這種情況必輸。 接下來的我就不懂了。。。。- = .= 十分鐘前 ,我現在模擬了幾組,懂了
前幾個奇異局勢有(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20) ...
性質就是每個自然數都會出現在一個且唯一的一個奇異局勢裏面 ,非奇異局勢可以經過適當操作轉化為奇異局勢
兩個數 不是奇異局勢 就是 非奇異局勢 ,當Alice面對奇異局勢時,必輸,反之,必贏
不是很懂的話 隨便想兩個數 模擬試試 多試幾組就好了
三)尼姆博弈
有三堆石頭,Alice和Bob輪流從其中一堆中取任意多的石頭,每次至少取一個石頭, Alice開始取,最後取完的贏,兩個人都是以最優的方案取,求最終贏的是誰
奇異局勢:第一個奇異局勢是(0,0,0),面對這種情況的一定輸。
設有a,b,c三堆石頭,當a^b^c == 0時就是奇異局勢,必輸。
非奇異局勢一定能變為奇異局勢,奇異局勢一定能變成非奇異局勢。
證明:
當a^b^c != 0時,a+b+c != 0,即還有石頭。
令a^b = x1,a^c = x2,b^c = x3,一定會存在至少下面的一種情況:
x1 < c,x2 < b,x3 < a。
假設x1 < c ,令c‘ =c – (c – x1) = x1,
得a^b^c’ == 0。
即非奇異局勢變為奇異局勢。
當a^b^c == 0 時,即a^b = c
只需要變成其中的任意一個數就可以當a^b != c。
即奇異局勢變為非奇異局勢。
由於(0,0,0)是最終得到的奇異局勢,並且沒有石頭可取,所以面對a^b^c== 0的狀態必輸。
就異或為零和不為零情況就是了
http://acm.hdu.edu.cn/showproblem.php?pid=1907 多組輸入 一個棋盤上黑白的位置 問誰嬴誰輸 不懂為啥得 a-b-1 ???
走子方式,於是我們通過sg函數的定義,爆出sg函數值就發現sg函數值就是兩個棋子的距離的絕對值減1.其實這個還是可以理解的,因為我們看當兩個棋子相鄰的時候,已經是一個必敗態了。 ???
#include <cstdio> #include <cstdlib> #include <cmath> #include <algorithm> int main() { int n,m,a,b; while(scanf("%d%d",&n,&m)!=EOF){ int cnt =0,flag =0; for(int i=0;i< n;i++){ scanf("%d%d",&a,&b); if(a < b)std::swap(a,b); if(a > 1)flag++;cnt ^= abs(a-b-1);//??? } printf(cnt==0 ?"BAD LUCK!\n":"I WIN!\n"); } return 0; }View Code
SG函數
SG函數可以看成各個Nim遊戲的和,將問題分成各種Nim問題,從而簡化了問題。
例如:有三堆石頭,每次能取的數量為斐波拉系數,即只能取1,2,3,5,8,13,21……這些石頭的數量。
定義mex運算:表示最小的不屬於這個集合的數。例如:mex{0,1,2,3} = 4、mex{0,1,3,4,5} = 2、mex{} = 0
對於任意的x,Sg(x) = mex{S},S為x的後繼狀態數值的集合,假設有三個狀態Sg(a)、Sg(b)、Sg(c)。那麽Sg(x) = mex{Sg(a),Sg(b),Sg(c)}。
設有三堆石頭為x、y、z這三堆。那麽只要判斷Sg(x)^Sg(y)^Sg(z) == 0就行,當成立時就說明面對的是奇異局勢,必輸,反正必贏。
/* 摘自jumping_frog 博客
首先定義mex(minimal excludant)運算,這是施加於一個集合的運算,表示最小的不屬於這個集合的非負整數。例如
mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
對於一個給定的有向無環圖,定義關於圖的每個頂點的Sprague-Grundy函數g如下:g(x)=mex{ g(y) | y是x的後繼},這裏的g(x)即
sg[x]。
例如:取石子問題,有1堆n個的石子,每次只能取{1,3,4}個石子,先取完石子者勝利,那麽各個數的SG值為多少?
sg[0]=0,f[]={1,3,4},
x=1時,可以取走1-f{1}個石子,剩余{0}個,mex{sg[0]}={0},故sg[1]=1;
x=2時,可以取走2-f{1}個石子,剩余{1}個,mex{sg[1]}={1},故sg[2]=0;
x=3時,可以取走3-f{1,3}個石子,剩余{2,0}個,mex{sg[2],sg[0]}={0,0},故sg[3]=1;
x=4時,可以取走4-f{1,3,4}個石子,剩余{3,1,0}個,mex{sg[3],sg[1],sg[0]}={1,1,0},故sg[4]=2;
x=5時,可以取走5-f{1,3,4}個石子,剩余{4,2,1}個,mex{sg[4],sg[2],sg[1]}={2,0,1},故sg[5]=3;
以此類推.....
x 0 1 2 3 4 5 6 7 8....
sg[x] 0 1 0 1 2 3 2 0 1....
計算從1-n範圍內的SG值。
f(存儲可以走的步數,f[0]表示可以有多少種走法)
f[]需要從小到大排序
1.可選步數為1~m的連續整數,直接取模即可,SG(x) = x % (m+1);
2.可選步數為任意步,SG(x) = x;
3.可選步數為一系列不連續的數,用getSG()計算
*/
不懂i SG函數 先放著 過段時間再來看 如果是費波納茨序列的話 只需數組開16 第十六個費波納茨數就1196
打表 和 dfs
// 獲得SG數組函數模板,t代表f數組的個數,n代表要求的sg數組上限 // f數組就是能取的個數(對於此題就是Fibonacci數列 // 有時,對於t已知就不需要單獨傳參 void get_sg(int t,int n) { int i,j; memset(sg,0,sizeof(sg)); for(i=1;i<=n;i++) { memset(mex,0,sizeof(mex)); // 對於屬於g(x)後繼的數置1 for( j=1 ;j<=t && fib[j]<=i ;j++ ) mex[sg[i-fib[j]]]=1; // 找到最小不屬於該集合的數 for( j=0 ; j<=n ; j++ ) if(!mex[j]) break; sg[i] = j; } }View 打表 Code
//註意 S數組要按從小到大排序 SG函數要初始化為-1 對於每個集合只需初始化1遍 //n是集合s的大小 S[i]是定義的特殊取法規則的數組 int s[N],sg[N],n; bool vis[N]; int dfs_SG(int x){ if(sg[x] != -1) return sg[x]; memset(vis,0,sizeof(vis)); for(int i = 0; i < n; ++i){ if(x >= s[i]){ dfs_SG(x-s[i]); vis[sg[x-s[i]]] = 1; } } for(int i = 0;; ++i){ if(!vis[i]){ e = i; return sg[x] = i; } } }View dfs Code
#include <bits/stdc++.h> // hdu 1848 using namespace std; const int N = 1010; int sg[N], mex[N]; int fa[55]; void getSG(int n) { for(int i = 1; i <= n; i ++) { memset(mex, 0, sizeof(mex)); for(int j = 1; fa[j] <= i; ++ j) mex[sg[i-fa[j]]] = 1; for(int j = 0; ; j ++) { if(!mex[j]) { sg[i] = j; break; } } } } int main() { int a, b, c; fa[1] = 1, fa[2] = 2; for(int i = 3; i < 55; i ++) fa[i] = fa[i-1] + fa[i-2]; getSG(1000); while(cin >> a >> b >> c) { if(a==0 && b==0 && c==0)break; if(sg[a] ^ sg[b] ^ sg[c])printf("Fibo\n"); else printf("Nacci\n"); } return 0; }View Code
#include <bits/stdc++.h> using namespace std; const int M = 105; int sg[M][M], vis[M], n, m; void fun() { memset(sg, 0, sizeof(sg)); for (int i = 1; i <= m; i++) for (int j = 1; j <= m; j++) { if (i == j) continue; memset(vis, 0, sizeof(vis)); if (i < j) { for (int k = i + 1; k < j; k++) vis[ sg[k][j] ] = 1, vis[ sg[i][k] ] = 1; } else if (i > j) { for (int k = j + 1; k < i; k++ ) vis[ sg[k][j] ] = 1, vis[ sg[i][k] ] = 1; } for (int k = 0;; k++) if (!vis[k]) { sg[i][j] = k; break; } } } int main() { int a, b; while (cin >> n >> m) { int ans = 0; fun(); while (n--) { scanf("%d%d", &a, &b); ans ^= sg[a][b]; } if (ans == 0) cout << "BAD LUCK!" << endl; else cout << "I WIN!" << endl; } }View 同上 一道題 Code
hdu1730 hdu1848 hdu1536 hdu2873
http://acm.hdu.edu.cn/showproblem.php?pid=1907
初學 博弈論 又稱對策論 Game Theory