博弈论
原文地址:https://wenku.baidu.com/view/74ffdb44bb68a98271fefa57.html
(一)巴什博奕(Bash Game):只有一堆n個物品,兩個人輪流從這堆物品中取物,規定每次至少取一個,最多取m個。最后取光者得勝。
顯然,如果n=m+1,那么由于一次最多只能取m個,所以,無論先取者拿走多少個,后取者都能夠一次拿走剩余的物品,后者取勝。因此我們發現了如何取勝的法則:如果n=(m+1)r+s,(r為任意自然數,s≤m),那么先取者要拿走s個物品,如果后取者拿走k(≤m)個,那么先取者再拿走m+1-k個,結果剩下(m+1)(r-1)個,以后保持這樣的取法,那么先取者肯定獲勝。總之,要保持給對手留下(m+1)的倍數,就能最后獲勝。
這個游戲還可以有一種變相的玩法:兩個人輪流報數,每次至少報一個,最多報十個,誰能報到100者勝。
取石子(一)
時間限制:3000 ms | 內存限制:65535 KB
難度:2
描述
一天,TT在寢室閑著無聊,和同寢的人玩起了取石子游戲,而由于條件有限,他/她們是用旺仔小饅頭當作石子。游戲的規則是這樣的。設有一堆石子,數量為N(1<=N<=1000000),兩個人輪番取出其中的若干個,每次最多取M個(1<=M<=1000000),最先把石子取完者勝利。我們知道,TT和他/她的室友都十分的聰明,那么如果是TT先取,他/她會取得游戲的勝利么?
輸入
第一行是一個正整數n表示有n組測試數據
輸入有不到1000組數據,每組數據一行,有兩個數N和M,之間用空格分隔。
輸出
對于每組數據,輸出一行。如果先取的TT可以贏得游戲,則輸出“Win”,否則輸出“Lose”(引號不用輸出)
樣例輸入
2
1000 1
1 100
樣例輸出
Lose
Win
最優解:
#include<iostream> using namespace std; int main() {int k;long m,n;cin>>k;while(k--){cin>>n>>m;if(n%(m+1)==0)cout<<"Lose"<<endl;elsecout<<"Win"<<endl;} }巴什博弈變形:
有兩種解,依實際情況而定:
取石子(七)
時間限制:1000 ms | 內存限制:65535 KB
難度:1
描述
Yougth和Hrdv玩一個游戲,拿出n個石子擺成一圈,Yougth和Hrdv分別從其中取石子,誰先取完者勝,每次可以從中取一個或者相鄰兩個,Hrdv先取,輸出勝利著的名字。
輸入
輸入包括多組測試數據。
每組測試數據一個n,數據保證int范圍內。
輸出
輸出勝利者的名字。
樣例輸入
2
3
樣例輸出
Hrdv
Yougth
解一:
尼姆博弈基本思想:
兩人從n堆物品中取任意個,先取完者勝。
即將n堆物品的數量異或,得到的值如果為0,則先手敗,反之先手勝。
如果要求先手在勝的條件下,到奇異局勢的方法數,則判斷異或的值與每一堆原值異或后(結果應該表示該堆沒有參加異或時的異或值)與原值比較大小,
如果小于,則方法數加一。且對應的方法后,該堆的數目應變為異或的值與每一堆原值異或的值。
取石子(二)
時間限制:3000 ms | 內存限制:65535 KB
難度:5
描述
小王喜歡與同事玩一些小游戲,今天他們選擇了玩取石子。
游戲規則如下:共有N堆石子,已知每堆中石子的數量,并且規定好每堆石子最多可以取的石子數(最少取1顆)。
兩個人輪流取子,每次只能選擇N堆石子中的一堆,取一定數量的石子(最少取一個),并且取的石子數量不能多于該堆石子規定好的最多取子數,等哪個人無法取子時就表示此人輸掉了游戲。
假設每次都是小王先取石子,并且游戲雙方都絕對聰明,現在給你石子的堆數、每堆石子的數量和每堆石子規定的單次取子上限,請判斷出小王能否獲勝。
輸入
第一行是一個整數T表示測試數據的組數(T<100)
每組測試數據的第一行是一個整數N(1
取石子(六)
時間限制:1000 ms | 內存限制:65535 KB
難度:3
描述
最近TopCoder的PIAOYI和HRDV很無聊,于是就想了一個游戲,游戲是這樣的:有n堆石子,兩個人輪流從其中某一堆中任意取走一定的石子,最后不能取的為輸家,注意: 每次只能從一堆取任意個,可以取完這堆,但不能不取。假設PIAOYI先取石子,請你幫他判斷他是否能贏(假設他們取的過程中不發生失誤,他們足夠聰明 )。
輸入
第一行輸入n,代表有n組測試數據(n<=10000)
以下每組測試數據包含兩行:第一行:包含一個整數m,代表本組測試數據有m(m<=1000)堆石子;
:第二行:包含m個整數Ai(Ai<=100),分別代表第i堆石子的數量。
輸出
若PIAOYI贏輸出“PIAOYI”,否則輸出“HRDV”注意每組結果占一行。。
樣例輸入
3
2
1 1
3
3 8 11
2
5 10
最優解:
#include<iostream> #include<stdio.h> using namespace std; void in(int &a) {char ch;while((ch=getchar())<'0'||ch>'9');for(a=0;ch>='0'&&ch<='9';ch=getchar()) a=a*10+ch-'0'; } int main() {int T;in(T);while(T--){int n;in(n);int ans=0;for(int i=0;i!=n;++i){int b;in(b);ans^=b;}if(ans) puts("PIAOYI");else puts("HRDV");}return 0; }取石子(三)
時間限制:1000 ms | 內存限制:1000 KB
難度:6
描述
小王喜歡與同事玩一些小游戲,今天他們選擇了玩取石子。
游戲規則如下:共有N堆石子,已知每堆中石子的數量,兩個人輪流取子,每次只能選擇N堆石子中的一堆,取一定數量的石子(最少取一個),取過子之后,還可以將該堆石子中剩下的任意多個石子中隨意選取幾個放到其它的任意一堆或幾堆上。等哪個人無法取子時就表示此人輸掉了游戲。注意,一堆石子沒有子之后,就不能再往此處放石子了。
假設每次都是小王先取石子,并且游戲雙方都絕對聰明,現在給你石子的堆數、每堆石子的數量,請判斷出小王能否獲勝。
例如:如果最開始有4堆石子,石子個數分別為3 1 4 2,而小王想決定要先拿走第三堆石子中的兩個石子(石子堆狀態變為3 1 2 2),然后他可以使石子堆達到的狀態有以下幾種:
3 1 2 2(不再移動石子)
4 1 1 2(移動到第一堆一個)
3 2 1 2(移動到第二堆一個)
3 1 1 3(移動到第四堆一個)
5 1 0 2(全部移動到第一堆)
3 3 0 2(全部移動到第二堆)
3 1 0 4(全部移動到最后)
輸入
可能有多組測試數據(測試數據組數不超過1000)
每組測試數據的第一行是一個整數,表示N(1<=N<=10)
第二行是N個整數分別表示該堆石子中石子的數量。(每堆石子數目不超過100)
當輸入的N為0時,表示輸入結束
輸出
對于每組測試數據,輸出Win表示小王可以獲勝,輸出Lose表示小王必然會敗。
樣例輸入
3
2 1 3
2
1 1
0
樣例輸出
Win
Lose
一般解:
分析:
顯然,如果石頭是能夠兩兩配對,每一對的數目相同,比如:2,3,2,4可以配對成(2,2),(4,4) ,這樣的話就是先拿的輸,因為后拿的可以使自己拿完后仍然能夠使得兩兩配對,且每一對的數目相同.
剛剛開始的時候,如果已經兩兩配對了,那么先拿的輸了,否則,選拿的可以把最大的動一下手腳,使剩下的兩兩配對,且每一對的數目相同.
最優解:
#include<iostream> #include<cstring> using namespace std; bool ok(int stone[]) {for(int i=0;i!=110;i++)if(stone[i]&1) return true;return false; } int main() {int stone[110];int n,m;while(cin>>n && n){memset(stone,0,sizeof(stone));while(n--){cin>>m;stone[m]++;}cout<<(ok(stone)?"Win":"Lose")<<endl;} }威佐夫博奕(Wythoff Game):有兩堆各若干個物品,兩個人輪流從某一堆或同時從兩堆中取同樣多的物品,規定每次至少取一個,多者不限,最后取光者得勝。
這種情況下是頗為復雜的。我們用(ak,bk)(ak ≤ bk ,k=0,1,2,…,n)表示兩堆物品的數量并稱其為局勢,如果甲面對(0,0),那么甲已經輸了,這種局勢我們稱為奇異局勢。前幾個奇異局勢是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。
可以看出,a0=b0=0,ak是未在前面出現過的最小自然數,而 bk= ak + k,奇異局勢有如下三條性質:
1。任何自然數都包含在一個且僅有一個奇異局勢中。
由于ak是未在前面出現過的最小自然數,所以有ak > ak-1 ,而 bk= ak + k > ak-1 + k-1 = bk-1 > ak-1 。所以性質1。成立。
2。任意操作都可將奇異局勢變為非奇異局勢。
事實上,若只改變奇異局勢(ak,bk)的某一個分量,那么另一個分量不可能在其他奇異局勢中,所以必然是非奇異局勢。如果使(ak,bk)的兩個分量同時減少,則由于其差不變,且不可能是其他奇異局勢的差,因此也是非奇異局勢。
3。采用適當的方法,可以將非奇異局勢變為奇異局勢。
假設面對的局勢是(a,b),若 b = a,則同時從兩堆中取走 a 個物體,就變為了奇異局勢(0,0);如果a = ak ,b > bk,那么,取走b - bk個物體,即變為奇異局勢;如果 a = ak , b < bk ,則同時從兩堆中拿走 ak - ab - ak個物體,變為奇異局勢( ab - ak , ab - ak+ b - ak);如果a > ak ,b= ak + k,則從第一堆中拿走多余的數量a - ak 即可;如果a < ak ,b= ak + k,分兩種情況,第一種,a=aj (j < k),從第二堆里面拿走 b - bj 即可;第二種,a=bj (j < k),從第二堆里面拿走 b - aj 即可。
從如上性質可知,兩個人如果都采用正確操作,那么面對非奇異局勢,先拿者必勝;反之,則后拿者取勝。
那么任給一個局勢(a,b),怎樣判斷它是不是奇異局勢呢?我們有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括號表示取整函數)奇妙的是其中出現了黃金分割數(1+√5)/2 = 1。618…,因此,由ak,bk組成的矩形近似為黃金矩形,由于2/(1+√5)=(√5-1)/2,可以先求出j=[a(√5-1)/2],若a=[j(1+√5)/2],那么a = aj,bj = aj + j,若不等于,那么a = aj+1,bj+1 = aj+1+ j + 1,若都不是,那么就不是奇異局勢。然后再按照上述法則進行,一定會遇到奇異
局勢。
取石子 (四)
時間限制:1000 ms | 內存限制:65535 KB
難度:4
描述
有兩堆石子,數量任意,可以不同。游戲開始由兩個人輪流取石子。游戲規定,每次有兩種不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在兩堆中同時取走相同數量的石子。最后把石子全部取完者為勝者。現在給出初始的兩堆石子的數目,如果輪到你先取,假設雙方都采取最好的策略,問最后你是勝者還是敗者。
輸入
輸入包含若干行,表示若干種石子的初始情況,其中每一行包含兩個非負整數a和b,表示兩堆石子的數目,a和b都不大于1,000,000,000。
輸出
輸出對應也有若干行,每行包含一個數字1或0,如果最后你是勝者,則為1,反之,則為0。
樣例輸入
2 1
8 4
4 7
樣例輸出
0
1
0
最優解:
Wythoff Game
時間限制:1000 ms | 內存限制:65535 KB
難度:1
描述
最近ZKC同學在學博弈,學到了一個偉大的博弈問題–威佐夫博弈。
相信大家都學過了吧?沒學過?沒問題。我將要為你講述一下這個偉大的博弈問題。
有兩堆石子,數量任意,可以不同。游戲開始由兩個人輪流取石子。
游戲規定,每次有兩種不同的取法:
一是可以在任意的一堆中取走任意多的石子;
二是可以在兩堆中同時取走相同數量的石子。
最后把石子全部取完者為勝者。
我們今天要做的是求前n個必敗態。
什么是必敗態?比如我們把(a,b)稱為一種狀態,a,b分別為兩堆石子中所剩的數目。如果a=0,b=0,我們說該種狀態為必敗態,因為我不能再進行游戲,即使是可以進行,那也是必敗的,你知道,游戲的我們都是非常聰明的。(0,0)(1,2)(3,5)…都是必敗態,我們今天要做的就是求前n個必敗態。不會?好吧!
我再告訴你:假設第n個必敗態為(a,b)a為前n-1個必敗態中沒有出現的最小自然數,b=a+n。這下大家應該明白了吧。好吧,我們的任務就的要前n個必敗態。規定第0個必敗態為(0,0)。
輸入
多組數據。
輸入為一個數n(0<=n<=100000)。
輸出
按照要求求出前n個必敗態。輸出格式看下面樣例。
樣例輸入
3
1
樣例輸出
(0,0)(1,2)(3,5)(4,7)
(0,0)(1,2)
提示
注意:每種情況中間沒有空格
取石子(八)
時間限制:1000 ms | 內存限制:65535 KB
難度:3
描述
有兩堆石子,數量任意,可以不同。游戲開始由兩個人輪流取石子。游戲規定,每次有兩種不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在兩堆中同時取走相同數量的石子。最后把石子全部取完者為勝者。現在給出初始的兩堆石子的數目,如果輪到你先取,假設雙方都采取最好的策略,問最后你是勝者還是敗者。如果你勝,你第1次怎樣取子?
輸入
輸入包含若干行,表示若干種石子的初始情況,其中每一行包含兩個非負整數a和b,表示兩堆石子的數目,a和b都不大于1,000,000。a=b=0退出。
輸出
輸出也有若干行,如果最后你是敗者,則為0,反之,輸出1,并輸出使你勝的你第1次取石子后剩下的兩堆石子的數量x,y,x<=y。如果在任意的一堆中取走石子能勝同時在兩堆中同時取走相同數量的石子也能勝,先輸出取走相同數量的石子的情況,假如取一堆的有多種情況,先輸出從石子多的一堆中取的情況,且要求輸出結果保證第二個值不小于第一個值。
樣例輸入
1 2
5 7
2 2
0 0
樣例輸出
0
1
3 5
3 5
4 7
1
0 0
1 2
最優解:
#include<iostream> #include <cstdio> #include <algorithm> #include<math.h> using namespace std; int main() {int a,b,temp,temp2,k,i;while(scanf("%d%d",&a,&b),a+b){if(a>b)swap(a,b);k=b-a;temp=k*(1.0+sqrt(5.0))/2.0;if(a==temp) //奇異局勢printf("0\n");else{printf("1\n");if(abs(temp-a)==abs(temp+k-b)&&temp<a) //兩堆printf("%d %d\n",temp,temp+k);if(a==0) //0 0情況,第一種奇異局勢printf("0 0\n");for(i=1;i<=b;i++) //一堆{temp=i*(1.0+sqrt(5.0))/2.0;temp2=temp+i;if(temp>b)break;if(temp==a&&temp2<b)printf("%d %d\n",a,temp2);else if(temp2==a&&temp<b)printf("%d %d\n",temp,a);else if(temp2==b&&temp<a)printf("%d %d\n",temp,b);}}}return 0; }Fibonacci’s Game(斐波那契博弈)
斐波那契博弈模型,大致上是這樣的:
有一堆個數為 n 的石子,游戲雙方輪流取石子,滿足:
1. 先手不能在第一次把所有的石子取完;
2. 之后每次可以取的石子數介于1到對手剛取的石子數的2倍之間(包含1和對手剛取的石子數的2倍)。
約定取走最后一個石子的人為贏家,求必敗態。
(轉)分析:
n = 2時輸出second;
n = 3時也是輸出second;
n = 4時,第一個人想獲勝就必須先拿1個,這時剩余的石子數為3,此時無論第二個人如何取,第一個人都能贏,輸出first;
n = 5時,first不可能獲勝,因為他取2時,second直接取掉剩下的3個就會獲勝,當他取1時,這樣就變成了n為4的情形,所以輸出的是second;
n = 6時,first只要去掉1個,就可以讓局勢變成n為5的情形,所以輸出的是first;
n = 7時,first取掉2個,局勢變成n為5的情形,故first贏,所以輸出的是first;
n = 8時,當first取1的時候,局勢變為7的情形,第二個人可贏,first取2的時候,局勢變成n為6得到情形,也是第二個人贏,取3的時候,second直接取掉剩下的5個,所以n = 8時,輸出的是second;
…………
從上面的分析可以看出,n為2、3、5、8時,這些都是輸出second,即必敗點,仔細的人會發現這些滿足斐波那契數的規律,可以推斷13也是一個必敗點。
借助“Zeckendorf定理”(齊肯多夫定理):任何正整數可以表示為若干個不連續的Fibonacci數之和。n=12時,只要誰能使石子剩下8且此次取子沒超過3就能獲勝。因此可以把12看成8+4,把8看成一個站,等價與對4進行”氣喘操作”。又如13,13=8+5,5本來就是必敗態,得出13也是必敗態。也就是說,只要是斐波那契數,都是必敗點。
所以我們可以利用斐波那契數的公式:fib[i] = fib[i-1] + fib[i-2],只要n是斐波那契數就輸出No。
取石子(五)
時間限制:1000 ms | 內存限制:65535 KB
難度:4
描述
himdd最近很想玩游戲,于是他找到acmj和他一起玩,游戲是這樣的:有一堆石子,兩個人輪流從其中取走一定的石子,取走最后所有石子的人為贏家,不過得遵循如下規則:
1.第一次取不能取完,至少取1顆.
2.從第二次開始,每個人取的石子數至少為1,至多為對手剛取的石子數的兩倍。
himdd事先想知道自己會不會贏,你能幫幫他嗎?(每次himdd先手)
輸入
有多組測試數據,每組有一個整數n(2<=n<2^64);
輸出
himdd會贏輸出Yes,否則輸出No;
樣例輸入
2
5
6
樣例輸出
No
No
Yes
一般解:
Nim Staircase博奕:
這個問題是尼姆博弈的拓展:游戲開始時有許多硬幣任意分布在樓梯上,共n階樓梯從地面由下向上編號為0到n。游戲者在每次操作時可以將樓梯j(1<=j<=n)上的任意多但至少一個硬幣移動到樓梯j-1上。游戲者輪流操作,將最后一枚硬幣移至地上(0號)的人獲勝。
算法:將奇數樓層的狀態異或,和為0則先手必敗,否則先手必勝。證明略。
例題:Poj1704
這道題可以把兩個棋子中間間隔的空格子個數作為一堆石子,則原題轉化為每次可以把左邊的一堆石子移到相鄰的右邊的一堆中。也就是階梯尼姆博弈,注意對輸入數據先排序,然后倒著往前數(a[n]-a[n-1]-1為第一個),奇數個數到的就做一下xor,其中最前面的看做a[1]-0-1,參考程序:
SG函數模板
首先定義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[]需要從小到大排序,這個模版f是從1開始的。hash數組大小跟f[]大小差不多
1.可選步數為1~m的連續整數,直接取模即可,SG(x) = x % (m+1);
2.可選步數為任意步,SG(x) = x;
3.可選步數為一系列不連續的數,用GetSG()計算
下邊補充一點東西。
上邊那個模版求hash時候并沒有考慮f[j]有效長度,在某些題目中可以通過,比如這個。因為在求斐波那契數列時候肯定多求了一個,而就是因為這個會在求hash時候打破循環。
其實總的來說還是這個函數并不嚴密。因為有的時候f[]是有有效長度的,如果多出了這個長度就會出現錯誤,如果你的初值都是0,那么就會取到0,如果是-1,那么就會取到-1,肯定不對。比如這個題。
下面這兩個模版應該就比較嚴密了,這個里邊的f[]是從零開始的。轉自:http://blog.csdn.net/primoblog/article/details/13376057
1、sg打表
//f[]:可以取走的石子個數 //sg[]:0~n的SG函數值 //hash[]:mex{} int f[K],sg[N],hash[N]; void getSG(int n) { memset(sg,0,sizeof(sg)); for(int i=1; i<=n; i++) { memset(hash,0,sizeof(hash)); for(int j=0; f[j]<=i && j < k; j++) //k是f[]的有效長度 hash[sg[i-f[j]]]=1; for(int j=0; ; j++) { //求mes{}中未出現的最小的非負整數 if(hash[j]==0) { sg[i]=j; break; } } } }2、Dfs
//注意 S數組要按從小到大排序 SG函數要初始化為-1 對于每個集合只需初始化1遍 //n是集合s的大小 S[i]是定義的特殊取法規則的數組 int s[N],sg[N],n; int getSG(int x) { if(sg[x]!=-1) return sg[x]; bool vis[M]; memset(vis,0,sizeof(vis)); for(int i=0; i<n; i++) { if(x>=s[i]) vis[getSG(x-s[i])]=1; } for(i=0;; i++) if(!vis[i]) { sg[x]=i; break; } return sg[x]; }博弈問題之SG函數博弈小結
2013-09-05 09:11:30 我來說兩句 作者:Bright-xl 收藏 我要投稿
SG函數:
給定一個有向無環圖和一個起始頂點上的一枚棋子,兩名選手交替的將這枚棋子沿有向邊進行移動,無法移 動者判負。事實上,這個游戲可以認為是所有Impartial Combinatorial Games的抽象模型。也就是說,任何一個ICG都可以通過把每個局面看成一個頂點,對每個局面和它的子局面連一條有向邊來抽象成這個“有向圖游戲”。下 面我們就在有向無環圖的頂點上定義Sprague-Garundy函數。首先定義mex(minimal excludant)運算,這是施加于一個集合的運算,表示最小的不屬于這個集合的非負整數。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
在我的理解中,sg函數就是一個對有向無環圖dfs的過程,在處理nim博弈時,多個石堆可以看成多個sg函數值的異或。
例題:
POJ2311 Cutting Game
典型的sg博弈,找后繼狀態。題意是給出一個n*m的紙片,每次剪成兩部分,誰先剪到1*1就勝利。這就是一個找后繼的題目,每次剪成的兩部分就是前一狀態的后繼,只要將兩個部分的sg值異或起來就是前一狀態的sg值。
POJ2425 A Chess Game
題意是給你一個拓撲圖,一個起點上的n個棋子,兩個玩家交替移動棋子,誰無法移動誰輸,典型的sg函數運用。套用模板就行了。此題數據量較大,加入了輸入優化后刷到了第一版第四名,nice!
[cpp]
POJ2068 Nim
題意是圓桌上有2n個人,奇數一隊,偶數一隊,每個人都有一個拿走棋子的最高限額,問你最后1對能否獲勝。
還是用強大的sg函數過的,記錄下每個狀態的sg。
POJ3537 Crosses and Crosses
題意:給出一個1*n的矩形,上面有n個方格,現有兩人分別在上面畫×,誰先能畫出三個×相連就贏了。這就是一個sg函數的母問題轉化為子問題的題目,由于在第x位置畫了×后,則就轉變成(x-3)個格子畫×和(n-x-2)個格子畫×。。。這就能不斷的分解下去,最后將所有sg值異或起來就是正解了。
[cpp]
POJ2599 A funny game
記憶化搜索,這題的博弈味道不濃,更多的是搜索。題意是給一個圖,兩人輪流移動,走過的節點不能再走。水題,dfs+標記就行。
[cpp]
組合博弈的通解就是sg函數,學習了sg函數之后一直沒有咋用過。
學習博弈的可以在nyoj上面做10道取石子題目,作為了對博弈也就有一定理解了。
用的時候注意初始的時候只要初始sg[0]=0;
其他都通過函數求解。
這里貼一個求解sg函數的模板。
int sg[N]; bool hash[N]; void sg_solve(int *s,int t,int N) //N求解范圍 S[]數組是可以每次取的值,t是s的長度。 {int i,j;memset(sg,0,sizeof(sg));for(i=1;i<=N;i++){memset(hash,0,sizeof(hash));for(j=0;j<t;j++)if(i - s[j] >= 0)hash[sg[i-s[j]]] = 1;for(j=0;j<=N;j++)if(!hash[j])break;sg[i] = j;} }用set容器實現的方法,原理一樣。oj上容易超時
void sg_solve() {memset(sg,0,sizeof(sg));for(int i=1;i<N;i++){set<int> v;for(int j=0;j<t;j++)if(i - s[j] >= 0)v.insert(sg[i-s[j]]);int g=0;while(v.count(g)!=0)g++;sg[i]=g;} }通過一道題目說一下。
hdoj 1536 和pku 2960 S-Nim
題意就是給出一個數組s。為每次可以取石子的數目。
然后給你n堆石子每堆si。求解先手能不能贏!標準的sg函數用法題目。
代碼:
#include<stdio.h> #include<string.h> #include <string> #include <iostream> using namespace std;const int N = 10008; int s[108],t; int sg[N]; bool hash[N]; void sg_solve(int *s,int t,int N) //N求解范圍 S[]數組是可以每次取的值,t是s的長度。 {int i,j;memset(sg,0,sizeof(sg));for(i=1;i<=N;i++){memset(hash,0,sizeof(hash));for(j=0;j<t;j++)if(i - s[j] >= 0)hash[sg[i-s[j]]] = 1;for(j=0;j<=N;j++)if(!hash[j])break;sg[i] = j;} }int main() {int i,j,n,m,h;while(scanf("%d",&t),t){string ans="";for(i=0;i<t;i++)scanf("%d",&s[i]);sg_solve(s,t,N);scanf("%d",&n);for(i=0;i<n;i++){scanf("%d",&m);int res = 0;for(j=0;j<m;j++){scanf("%d",&h);res ^= sg[h];}ans+=res?'W':'L';}cout<<ans<<endl;}return 0; }總結
- 上一篇: 配置 Sybase数据源
- 下一篇: configure: error: Cu