博弈論入門小結
感受到了被博弈論支配的恐懼……
入門的話個人按順序推薦幾篇論文:
《由感性認識到理性認識——透析一類搏弈遊戲的解答過程》張一飛
《解析一類組合遊戲》 王曉珂
《組合遊戲概述—淺談SG遊戲的若幹拓展及變形》 賈誌豪
看完這三篇還是要有點時間的,然而博主很傻的倒著看完了,然後就成功地完成了入門到放棄,事倍功半……
到現在為止博弈論做了7道題,感覺只是大致學了一點皮毛,以後肯定回去再做一些題目,然而Hz書店沒有和博弈論有關的書,差評。
只能說博弈論是一個很奇妙的東西,並沒有什麽板子,可以和多種知識點結合,所謂的套路也並不是那麽靠譜,從某種意義上講就是“比智商”。而且要學會發散思維,有時如果死扣一個小局面很有可能徹底困死,在這裏把幾道做過的認為比較有趣的題目及題解放出來分享一下。
Bzoj 1188 分裂遊戲
一道論文題。在論文裏有詳細解釋。(膜拜一發呵呵酵母菌,上來自己推出來的%%%)
對於這種只關註輸贏而且先不能走的輸的遊戲我們一般都是按照“對稱”思想。
在這道題裏我們同樣利用對稱思想。如果先手者一個石子數為偶數的堆裏的石子進行操作,後手者只要模仿一下可以發現局面實質還是不會變的。因此我們可以把所有石子堆按照個數奇偶簡化問題,使所有堆的石子個數都不大於1。然後我們開始happy的推sg值。既然我們每次操作只能把石子放在右側的堆裏,每一個石子的後繼局面就是從後面石子的sg值裏面取出兩個異或一下就可以代表後繼局面了。然後,只要把所有有石子的堆的sg值異或一下結果就出來了(為了方便,我們在一開始倒序預處理)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <cstring> 6 #define N 30 7 using namespace std; 8 int T,n,sg[N],a[N]; 9 bool mark[N*N]; 10 void init() 11 { 12 sg[0]=0; 13 for(int i=1;i<=N-4;i++) 14 { 15Bzoj 1188for(int j=i-1;j>=0;j--) 16 mark[sg[i-1]^sg[j]]=1; 17 for(int j=0;j<=N*N-2;j++) 18 { 19 if(!mark[j]) 20 { 21 sg[i]=j; 22 break; 23 } 24 } 25 } 26 } 27 int main() 28 { 29 init(); 30 scanf("%d",&T); 31 while(T--) 32 { 33 scanf("%d",&n); 34 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 35 int ans=0; 36 for(int i=1;i<=n;i++) 37 { 38 if(a[i]&1) 39 { 40 ans^=sg[n-i]; 41 } 42 } 43 int js=0; 44 bool bj=0; 45 for(int i=1;i<=n;i++) 46 { 47 if(!a[i])continue; 48 for(int j=i+1;j<=n;j++) 49 { 50 for(int k=j;k<=n;k++) 51 { 52 if((ans^sg[n-i]^sg[n-j]^sg[n-k])==0) 53 { 54 js++; 55 if(!bj) 56 { 57 printf("%d %d %d\n",i-1,j-1,k-1); 58 bj=1; 59 } 60 } 61 } 62 } 63 } 64 if(!bj) printf("-1 -1 -1\n"); 65 printf("%d\n",js); 66 } 67 return 0; 68 }
Bzoj 2017 [Usaco2009 Nov] 硬幣遊戲
一道博弈DP題……
這道題基本一看就可以看出大致的轉移方程。然而博主一開始多加了一個現在是誰先手然後就由於迷之轉移和迷之最終答案撲街了……
我們可以倒著dp這道題。設f[i][j]為當前剩i個硬幣,上一次取j個,接著往下取最多能得多少錢,則f[i][j]=max(f[i-k][k]+sum[i][k])(k<=j*2)這看似是n^3的dp我們可以通過借助之前的f[i][j-1]優化轉移到n^2,在此就不多贅述了。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <cstring> 6 #include <algorithm> 7 #define N 2005 8 using namespace std; 9 int n,a[N]; 10 int f[N][N]; 11 int sum[N]; 12 int main() 13 { 14 scanf("%d",&n); 15 for(int i=n;i>=1;i--) scanf("%d",&a[i]); 16 for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i]; 17 for(int i=1;i<=n;i++) 18 { 19 for(int j=1;j<=n;j++) 20 { 21 f[i][j]=f[i][j-1]; 22 if(i>=2*j) f[i][j]=max(f[i][j],sum[i]-f[i-j*2][j*2]); 23 if(i>=2*j-1) f[i][j]=max(f[i][j],sum[i]-f[i-(j*2-1)][j*2-1]); 24 } 25 } 26 printf("%d",f[n][1]); 27 return 0; 28 }View Code
Bzoj 2281 [Sdoi2011]黑白棋
之前寫過專門的題解……
Bzoj 1982 Moving Pebbles
一道讓人崩潰的題。友情提示:本道題最高級的算法是sort,如果你不想像本蒟蒻一樣崩潰那最好再想一想……
先解釋一下題意:這裏有n堆石子,每次你選擇一個還有石子的石子堆先拿走一個,然後拿出來任意個,再從任意個裏面取出任意個,放到任意還有石子的堆中,其余扔掉。
仍然是按照慣例,“對稱”思想。我們思考一下什麽時候我們能夠被不斷“模仿”,最基礎的就是按照石子數相同所有堆都可以兩兩配成一對。然後完全模仿對方的操作。那麽除了這種方法還有別的必敗狀態嗎?
讓我們分類討論一下。
首先,是單數個石子堆。我們將石子排序,第一小和第二小,第三小和第四小……最後晾出最大的石子堆。對最大的石子堆進行操作,顯然肯定能夠將其他石子都補成兩兩一致。使對方面臨必敗狀態。
當石子堆數為雙數且不滿足上面所說狀態時。我們將最大和最小,第二小和第三小,第四小和第五小……進行配對。顯然仍然是可以通過對第一大的堆進行操作,仍然可以補成兩兩相同。
所以我們只要sort一下就好了……
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <cstring> 6 #include <algorithm> 7 #define N 100005 8 using namespace std; 9 int n,a[N]; 10 int main() 11 { 12 scanf("%d",&n); 13 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 14 if(n&1) 15 { 16 printf("first player\n"); 17 } 18 else 19 { 20 bool flag=1; 21 sort(a+1,a+n+1); 22 for(int i=1;i<=n;i+=2) 23 { 24 if(a[i]!=a[i+1]) 25 { 26 flag=0; 27 break; 28 } 29 } 30 if(!flag) printf("first player\n"); 31 else printf("second player\n"); 32 } 33 return 0; 34 }View Code
Bzoj 2437 兔兔與蛋蛋
這道題是一道二分圖博弈,然而爆搜能拿75分……考場上要是想不到二分圖博弈爆搜還是很不錯滴……
我們可以先證明出來我們白格永遠不會回到之前到過的格子,因為在四聯通的格子裏環永遠是偶數個格子。那麽如果我們將每一個白格和他四周的黑格進行連邊,就會發現他是一個經典的二分圖博弈。我們只要每次判斷一下就好了。如果你不知道二分圖博弈的話推薦一發這個博客:BZOJ 1443 遊戲(二分圖博弈) 講的還是挺不錯的。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <queue> 6 #include <iostream> 7 #define N 50 8 using namespace std; 9 int p,n,zz2,m,bh[N][N],ma[N][N]; 10 char bb[N]; 11 int zz,jg[1005],a[N*N]; 12 struct ro{ 13 int to,next; 14 }road[N*N*N]; 15 void build(int x,int y) 16 { 17 zz++; 18 road[zz].to=y; 19 road[zz].next=a[x]; 20 a[x]=zz; 21 } 22 int zy[10][2]; 23 void bui() 24 { 25 memset(a,0,sizeof(a)); 26 zz=0; 27 for(int i=1;i<=n;i++) 28 { 29 for(int j=1;j<=m;j++) 30 { 31 int tx,ty; 32 for(int k=1;k<=4;k++) 33 { 34 tx=i+zy[k][0],ty=j+zy[k][1]; 35 if(!tx||!ty||tx==n+1||ty==m+1) continue; 36 if(ma[tx][ty]!=ma[i][j]&&ma[tx][ty]&&ma[i][j]) 37 { 38 build(bh[i][j],bh[tx][ty]); 39 } 40 } 41 } 42 } 43 } 44 int b[N*N]; 45 bool fw[N*N],bj[N*N],cz[N*N]; 46 bool find(int x) 47 { 48 for(int i=a[x];i;i=road[i].next) 49 { 50 int y=road[i].to; 51 if(fw[y]||cz[y]) continue; 52 fw[y]=1; 53 if(!b[y]||find(b[y])) 54 { 55 b[y]=x; 56 bj[y]=1; 57 return 1; 58 } 59 } 60 return 0; 61 } 62 bool check(int x,int y) 63 { 64 int js=0; 65 memset(bj,0,sizeof(bj)); 66 memset(b,0,sizeof(b)); 67 for(int i=1;i<=n;i++) 68 { 69 for(int j=1;j<=m;j++) 70 { 71 if(ma[i][j]==2) 72 { 73 memset(fw,0,sizeof(fw)); 74 if(find(bh[i][j])) js++; 75 } 76 } 77 } 78 if(bj[bh[x][y]]) 79 { 80 int js2=0; 81 cz[bh[x][y]]=1; 82 memset(b,0,sizeof(b)); 83 for(int i=1;i<=n;i++) 84 { 85 for(int j=1;j<=m;j++) 86 { 87 if(ma[i][j]==2) 88 { 89 memset(fw,0,sizeof(fw)); 90 if(find(bh[i][j])) js2++; 91 } 92 } 93 } 94 cz[bh[x][y]]=0; 95 if(js==js2) return 1; 96 else return 0; 97 } 98 else return 1; 99 } 100 int main() 101 { 102 scanf("%d%d",&n,&m); 103 int sx,sy; 104 for(int i=1;i<=n;i++) 105 { 106 scanf("%s",bb+1); 107 for(int j=1;j<=m;j++) 108 { 109 if(bb[j]==‘.‘) ma[i][j]=0,sx=i,sy=j; 110 else if(bb[j]==‘O‘) ma[i][j]=1; 111 else ma[i][j]=2; 112 bh[i][j]=(i-1)*m+j; 113 } 114 } 115 116 zy[1][0]=1,zy[1][1]=0; 117 zy[2][0]=-1,zy[2][1]=0; 118 zy[3][0]=0,zy[3][1]=1; 119 zy[4][0]=0,zy[4][1]=-1; 120 scanf("%d",&p); 121 for(int i=1;i<=p;i++) 122 { 123 int x,y; 124 scanf("%d%d",&x,&y); 125 bui(); 126 if(!check(x,y)) 127 { 128 for(int j=1;j<=4;j++) 129 { 130 int tx=sx+zy[j][0],ty=sy+zy[j][1]; 131 if(!tx||!ty||tx==n+1||ty==m+1||(tx==x&&ty==y)) continue; 132 if(ma[tx][ty]==1) 133 { 134 if(check(tx,ty)) 135 { 136 zz2++; 137 jg[zz2]=i; 138 break; 139 } 140 } 141 } 142 } 143 swap(ma[sx][sy],ma[x][y]); 144 sx=x,sy=y; 145 scanf("%d%d",&x,&y); 146 swap(ma[x][y],ma[sx][sy]); 147 sx=x,sy=y; 148 } 149 printf("%d\n",zz2); 150 for(int i=1;i<=zz2;i++) 151 { 152 printf("%d\n",jg[i]); 153 } 154 return 0; 155 }View Code
Bzoj 4035 數組遊戲
這是一個幽默的故事。
博主一開始想到了當n/x相等時兩個格子本質相同,然後就走向了“找對稱”的不歸路。這時候就體現出了思維的重要性了,事實證明博主並沒有。我們可以回到一個很基礎的函數:SG函數。
由於網上十分優秀的資料很多,關於SG函數我就不贅述了。
我們嘗試著將問題轉化:如果我們可以直接翻轉黑格子,將失敗條件改為全都是黑格子,問題是否會發生變化呢?很明顯,如果翻完只有那一個白格子說明之前遊戲早就結束了,肯定不對。如果不是,我們翻轉黑格子一定會對自己有利,那麽對方一定會給你再翻回來,然後兩個傻蛋就這麽死磕了……所以我們並不會去翻黑格子,問題並沒有發生本質變化。但是我們就可以由於“只能翻白格子”的取消,我們可以對每一個點一視同仁的求sg函數。此時當我們以翻轉一個棋子(包括順帶著翻他後面的)看做一個子遊戲,那麽他的後繼狀態就是2x、3x……所以sg[x]=mex(sg[2*x],sg[2*x]^sg[3*x],sg[2*x]^sg[3*x]^sg[4*x],……)現在就比較好理解了。
接下來我們的目的就是求SG函數。正如上面所言,所有n/x相等的數本質都是一樣的。所以我們在這裏可以使用除法分塊。我們先枚舉n/x的值,然後依次枚舉題目描述裏的k。直接說k或許並不是太準確,與其說枚舉k,更好的說法是枚舉每一個sg值相同的k的區間的起始k值。也就是說有很多k值的sg值相等(也正是這點,使我們的時間復雜度並不是O(n),話說誰能幫忙證一下到底是多少?)對於每一組數據直接異或上對應節點的sg值便是了。(代碼裏會有註釋)
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 #include<cmath> 7 #define N 100000+10 8 using namespace std; 9 int n,m; 10 int ch(int x,int y) 11 { 12 return (x==y)?y+1:y/(y/(x+1)); 13 } 14 int c[2][N]; 15 int zz,jg[N]; 16 bool vis[N]; 17 void init() 18 { 19 for(int i=1;i<=n;i=ch(i,n))//枚舉n/x的值 20 { 21 zz=0; 22 int now=0; 23 for(int j=2;j<=i;j=ch(j,i))//再次進行分塊 ,註意從2開始 24 { 25 int x=i/j;//這裏的x實際上就是題目裏的k 26 int t=(x>m)?c[1][n/x]:c[0][x];//在這裏體現出的j的分塊的實際意義 27 zz++; 28 jg[zz]=now^t; 29 vis[jg[zz]]=1; 30 if((i/x-(i/(x+1)))&1) now^=t;//如果塊內的k的個數是奇數個那麽對於now需要異或上結果 31 } 32 now=1; 33 while(vis[now]) now++; 34 if(i>m) c[1][n/i]=now;//由於數組大小原因,記錄兩套,一套是i>m,這時候意味著當前n/i只對應著一個自然數 35 else c[0][i]=now;//另一套意味著對應一套自然數。 36 for(int j=1;j<=zz;j++) vis[jg[j]]=0;//清零 37 } 38 } 39 int main() 40 { 41 42 scanf("%d",&n); 43 m=sqrt(n); 44 init(); 45 int T; 46 scanf("%d",&T); 47 while(T--) 48 { 49 int sm,an=0; 50 scanf("%d",&sm); 51 for(int i=1;i<=sm;i++) 52 { 53 int x; 54 scanf("%d",&x); 55 x=n/x; 56 if(x>m) an^=c[1][n/x];//判斷一下x的大小 57 else an^=c[0][x]; 58 } 59 if(!an) printf("No\n"); 60 else printf("Yes\n"); 61 } 62 return 0; 63 }View Code
博弈論入門小結