博弈-Nim博弈

Nim博弈-屬於組合遊戲

條件:

1、有兩名選手;

      2、兩名選手交替對遊戲進行移動(move),每次一步,選手可以在(一般而言)有限的合法移動集合中任選一種進行移動;

      3、對於遊戲的任何一種可能的局面,合法的移動集合只取決於這個局面本身,不取決於輪到哪名選手操作、以前的任何操作、骰子的點數或者其它什麼因素; 

      4、如果輪到某名選手移動,且這個局面的合法的移動集合爲空(也就是說此時無法進行移動),則這名選手負。根據這個定義,很多日常的遊戲並非ICG。例如象棋就不滿足條件3,因爲紅方只能移動紅子,黑方只能移動黑子,合法的移動集合取決於輪到哪名選手操作。


定義:

通常的Nim遊戲的定義是這樣的:有若干堆石子,每堆石子的數量都是有限的,合法的移動是“選擇一堆石子並拿走若干顆(不能不拿)”,如果輪到某個人時所有的石子堆都已經被拿空了,則判負(因爲他此刻沒有任何合法的移動)。
這遊戲看上去有點複雜,先從簡單情況開始研究吧。如果輪到你的時候,只剩下一堆石子,那麼此時的必勝策略肯定是把這堆石子全部拿完一顆也不給對手剩,然後對手就輸了。如果剩下兩堆不相等的石子,必勝策略是通過取多的一堆的石子將兩堆石子變得相等,以後如果對手在某一堆裏拿若干顆,你就可以在另一堆中拿同樣多的顆數,直至勝利。如果你面對的是兩堆相等的石子,那麼此時你是沒有任何必勝策略的,反而對手可以遵循上面的策略保證必勝。如果是三堆石子……好像已經很難分析了,看來我們必須要藉助一些其它好用的(最好是程式化的)分析方法了,或者說,我們最好能夠設計出一種在有必勝策略時就能找到必勝策略的算法。
定義P-position和N-position,其中P代表Previous,N代表Next。直觀的說,上一次move的人有必勝策略的局面是P-position,也就是“後手可保證必勝”或者“先手必敗”,現在輪到move的人有必勝策略的局面是N-position,也就是“先手可保證必勝”。更嚴謹的定義是:1.無法進行任何移動的局面(也就是terminal position)是P-position;2.可以移動到P-position的局面是N-position;3.所有移動都導致N-position的局面是P-position。
按照這個定義,如果局面不可能重現,或者說positions的集合可以進行拓撲排序,那麼每個position或者是P-position或者是N-position,而且可以通過定義計算出來。
結論:
(Bouton's Theorem)對於一個Nim遊戲的局面(a1,a2,...,an),它是P-position當且僅當a1^a2^...^an=0,其中^表示異或(xor)運算。
證明:
根據定義,證明一種判斷position的性質的方法的正確性,只需證明三個命題:
 1、這個判斷將所有terminal position判爲P-position;
2、根據這個判斷被判爲N-position的局面一定可以移動到某個P-position;
3、根據這個判斷被判爲P-position的局面無法移動到某個P-position。
第一個命題顯然,terminal position只有一個,就是全0,異或仍然是0。

第二個命題,對於某個局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某個合法的移動,將ai改變成ai'後滿足a1^a2^...^ai'^...^an=0。不妨設a1^a2^...^an=k,則一定存在某個ai,它的二進制表示在k的最高位上是1(否則k的最高位那個1是怎麼得到的)。這時ai^k<ai一定成立。則我們可以將ai改變成ai'=ai^k,此時a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。

第三個命題,對於某個局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某個合法的移動,將ai改變成ai'後滿足a1^a2^...^ai'^...^an=0。因爲異或運算滿足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以將ai改變成ai'不是一個合法的移動。證畢。
根據這個定理,我們可以在O(n)的時間內判斷一個Nim的局面的性質,且如果它是N-position,也可以在O(n)的時間內找到所有的必勝策略。

題目:
1.
描述 Here is a simple game. In this game, there are several piles of matches and two players. The two player play in turn. In each turn, one can choose a pile and take away arbitrary number of matches from the pile (Of course the number of matches, which is taken away, cannot be zero and cannot be larger than the number of matches in the chosen pile). If after a player’s turn, there is no match left, the player is the winner. Suppose that the two players are all very clear. Your job is to tell whether the player who plays first can win the game or not. 輸入 The input consists of several lines, and in each line there is a test case. At the beginning of a line, there is an integer M (1 <= M <=20), which is the number of piles. Then comes M positive integers, which are not larger than 10000000. These M integers represent the number of matches in each pile. 輸出 For each test case, output "Yes" in a single line, if the player who play first will win, otherwise output "No". 樣例輸入
2 45 45

3 3 6 9
樣例輸出
No

Yes

基礎的Nim博弈問題:
(1)如果輪到某人取火柴的時候,火柴已經沒有了,那麼此人輸,設爲P-格局
       (2)如果輪到某人取火柴的時候,他能夠取完火柴,那麼此人贏,設爲N-格局
       (3)如果輪到某人取火柴的時候,他能夠將當前格局轉化爲某個P格局,那麼此格局爲N-格局
       (4)如果輪到某人取火柴的時候,他不能將當前格局轉化爲某個P格局,那麼此格局爲P-格局
下面我們來推導一個定理:一個格局記爲(x1,x2,...,xn),且次序無關,此格局爲 P-格局 當且僅當 x1^x2^...^xn = 0.其中^是按位異或運算
推導:  對應上述4種情形(爲了敘述的簡潔,並不十分嚴謹地證明,而是直接假設結論正確,再說明定理的工作機制)
            (1)當(x1,x2,...,xn)中全爲0時            ,格局爲P-格局,此時x1 ^ x2 ^ ... ^ xn = 0成立。
            (2)當(x1,x2,...,xn)中只有一個不爲0,格局爲N-格局,此時x1 ^ x2 ^ ... ^ xn = 0不成立。
            (3)當(x1,x2,...,xn)是P-格局時,x1,...,xn不全爲0.(反證法)
                                          假設x1 ^ x2 ^ ... ^ xn = p,且p不爲0,
                                          記 p 的二進制表示中,最左的1(最高位的1)在從左至右數第 k 位.
                                          由於p是異或運算的結果,那麼 x1, x2 , ... , xn中至少有一個數第k位爲1,
                                          不妨設 xi 的第 k 位爲1,那麼 xi ^ p 第 k 位爲0,那麼xi > xi^p 顯然成立.
                                          也就是說,存在某種取法,使i堆的火柴數變化到 xi^p .
                                          題設x1 ^ x2 ^ ... ^ xn = p,那麼x1 ^ x2 ^ ... ^ xn ^ p = 0.
                                          那麼當前格局可以轉化到某個P-格局,也就是說當前格局時N-格局,矛盾
                                          所以,必有p=0.
                (4)當x1 ^ x2 ^ ... ^ xn = 0時,如果存在某個移動 xi 變化到 xi ’ ,且x1^x2^....^xi ' ^...^xn = 0,
                     那麼由異或運算的消去律有,xi = xi ' ,也就是說一根火柴都沒取,這不允許的,所以
                     當前格局只能是P格局
nyoj585-取石子-相同的題目:
#include <iostream>
#include <stdio.h>
using namespace std;
int pile[1010];
int main(){
    int nc;
    //cin>>nc;
    scanf("%d",&nc);
    while(nc--){
	int M;scanf("%d",&M);
	int result = 0;
		for(int i=0; i<M; ++i)
        {
            scanf("%d",&pile[i]);
           // cin>>pile[i];
        result ^= pile[i];
        }
		if(result)
            puts("PIAOYI\n");
            //cout<<"PIAOYI"<<endl;
		else
            puts("HRDV\n");
		   //  cout<<"HRDV" <<endl;
    }
	return 0;
}


變形:nyoj888

取石子(九)

時間限制:1000 ms  |  內存限制:65535 KB
難度:4
描述

最近TopCoder的Yougth和Hrdv在玩一個遊戲,遊戲是這樣的。

n堆石子,兩個人輪流從其中某一堆中任意取走一定的石子,最後不能取的爲贏家,注意: 每次只能從一堆取任意個,可以取完這堆,但不能不取。

假設Yougth先取,輸入贏了的人名字、

輸入
第一行輸入n,代表有n組測試數據(n<=10000)
以下每組測試數據包含兩行:第一行:包含一個整數m,代表本組測試數據有m(m<=1000)堆石子;
:第二行:包含m個整數Ai(Ai<=10000),分別代表第i堆石子的數量。
輸出
若Yougth贏輸出“Yougth”,否則輸出“Hrdv”注意每組結果佔一行。。
樣例輸入
3
2
1 1
3
3 8 11
2
5 10
樣例輸出
Yougth
Hrdv
Yougth


anti-nim遊戲
這題與以往的博弈題的勝負條件不同,誰走完最後一步誰輸
  首先給出結論: 先手勝當且僅當
(1)所有堆石子數都爲1且遊戲的result值爲0 ,(2)存在某堆石子數大於1且遊戲的result值不爲0
證明:
1。顯然成立。
       2。分兩種情況
            a.只有一堆石子>1。你可以把它變成1。
            b.至少兩隊石子>1,你只需要把SG值邊爲0就可以了,這個操作之後,至少還有兩堆石子>1,然後對方隨便怎麼操作,都會把SG變成非0
           還需要證明1,2的反面是必敗的。
           1顯然。
           2你會把SG變成非0,而且因爲現在至少兩堆石子>1了,所以你還會給人家至少留一個>1的,那麼無論怎麼搞,都會送給後手一個必勝態。


代碼:
#include<stdio.h>
int main()
{
    int T,n,a,i,result,count;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        result=0;
        count=0;
        for(i=1; i<=n; i++)
        {
            scanf("%d",&a);
            result=result^a;
            if(a>1)
                count++;
        }
        if((count&&result)||(!count&&!result))
            printf("Yougth\n");
        else
            printf("Hrdv\n");
    }
    return 0;
}


2.輸出可以贏的第一步走的方法數
桌子上有M堆撲克牌;每堆牌的數量分別爲Ni(i=1…M);兩人輪流進行;每走一步可以任意選擇一堆並取走其中的任意張牌;桌子上的撲克全部取光,則遊戲結束;最後一次取牌的人爲勝者。
現在我們不想研究到底先手爲勝還是爲負,我只想問大家:
——“先手的人如果想贏,第一步有幾種選擇呢?”
Input
輸入數據包含多個測試用例,每個測試用例佔2行,首先一行包含一個整數M(1<M<=100),表示撲克牌的堆數,緊接着一行包含M個整數Ni(1<=Ni<=1000000,i=1…M),分別表示M堆撲克的數量。M爲0則表示輸入數據的結束。
Output
如果先手的人能贏,請輸出他第一步可行的方案數,否則請輸出0,每個實例的輸出佔一行。
Sample Input
3 5 7 9 0 
Sample Output
1


輸出先手能贏的方法數
我們知道在Nim博弈中
如果我們面對的是一個非奇異局勢(a,b,c),要如何變爲奇異局勢呢?假設 a < b< c,我們只要將 c 變爲 a^b,即可,因爲有如下的運算結果: a^b^(a^b)=(a^a)^(b^b)=0^0=0。要將c 變爲a^b,只要從 c中減去 c-(a^b)即可。
也就是說,其中一個堆C必須大於所有其他所有堆異或的值,由此我們可以進行枚舉
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int main()
{
    int n,a[105],i,j,cnt;
    while(~scanf("%d",&n),n)
    {
        cnt = 0;
        for(i = 0; i<n; i++)
            scanf("%d",&a[i]);
        for(i = 0; i<n; i++)
        {
            int s = 0;
            for(j = 0; j<n; j++)
            {
                if(i!=j)
                    s^=a[j];
            }
            if(a[i] > s)
                cnt++;
        }
        printf("%d\n",cnt);
    }

    return 0;
}

3.輸出上述的第一步走法:
Problem Description
m堆石子,兩人輪流取.只能在1堆中取.取完者勝.先取者負輸出No.先取者勝輸出Yes,然後輸出怎樣取子.例如5堆 5,7,8,9,10先取者勝,先取者第1次取時可以從有8個的那一堆取走7個剩下1個,也可以從有9個的中那一堆取走9個剩下0個,也可以從有10個的中那一堆取走7個剩下3個.
Input
輸入有多組.每組第1行是m,m<=200000. 後面m個非零正整數.m=0退出.
Output
先取者負輸出No.先取者勝輸出Yes,然後輸出先取者第1次取子的所有方法.如果從有a個石子的堆中取若干個後剩下b個後會勝就輸出a b.參看Sample Output.
Sample Input
2 45 45 3 3 6 9 5 5 7 8 9 10 0
Sample Output
No Yes 9 5 Yes 8 1 9 0 10 3

求出在第i堆取完石子後應該剩下多少個與其它堆相異或等於0,這是留給對手的是必敗態,所以只要求出其它堆的異或值就好了,因爲兩個相同的數異或以後纔是0。
代碼:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int a[200005],ans[200005][2];

int main()
{
    int n,i,j,cnt,s;
    while(~scanf("%d",&n),n)
    {
        cnt = 0;
        s = 0;
        for(i = 0; i<n; i++)
        {
            scanf("%d",&a[i]);
            s^=a[i];
        }
        for(i = 0; i<n; i++)
        {
            if(a[i] > (s^a[i]))
            {
                ans[cnt][0] = a[i];
                ans[cnt][1] = s^a[i];
                cnt++;
            }
        }
        if(cnt)
        {
            printf("Yes\n");
            for(i = 0; i<cnt; i++)
                printf("%d %d\n",ans[i][0],ans[i][1]);
        }
        else
            printf("No\n");
    }

    return 0;
}


題目描述:
兩個人玩遊戲,在標有1,2,3,4,5...的格子上有一些棋子,規則是選一枚棋子移動,要求不能跨越棋子移動,必需向左移動(可以移動任意格),不能移動的就輸掉比賽。
下面是一個棋局的例子:
+--+--+--+--+--+--+--+--+--+
|1x|2 |3x|4 |5 |6x|7x|8 |9 |(旁邊標有x的表示在這裏有棋子)
+--+--+--+--+--+--+--+--+--+
給出棋子的初始位置,若先手勝,輸出"Georgia will win",否則輸出"Bob will win"(不含引號)。
輸入格式:
多組數據。
對於每組數據,第一行是有一個T,表示有T組數據。
對於每組數據,第一行有一個N,表示有N枚棋子。
接下來的一行有N個數,分別爲每個棋子的位置。
輸出格式:
對每組數據,輸出一行,如題目描述那樣。
樣例輸入:
2
3
1 2 3
8
1 5 6 7 9 12 14 17
樣例輸出:
Bob will win
Georgia will win

核心思想在於想熟知的Nim遊戲進行轉化,可以把空格數作爲石子數,每一步既可以從任一堆石子中取若干,也可以將最右側的石子扔掉。而且經分析可知,偶數位置的石子實際上不發揮任何作用。

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int t,n,sum,position[10005];
	cin>>t;
	while(t--){
		cin>>n;
		position[0]=0;
		for(int i=1;i<=n;i++)
			cin>>position[i];
		sort(position,position+n+1);
		sum=0;
		for(int i=n;i>0;i-=2){
			sum=sum^(position[i]-position[i-1]-1);
		}
		if(sum==0)
			cout<<"Bob will win"<<endl;
		else cout<<"Georgia will win"<<endl;
	}
	//system("pause");
	return 0;
} 


高僧鬥法:
歷屆試題 高僧鬥法  
時間限制:1.0s   內存限制:256.0MB
問題描述
  古時喪葬活動中經常請高僧做法事。儀式結束後,有時會有“高僧鬥法”的趣味節目,以舒緩壓抑的氣氛。
  節目大略步驟爲:先用糧食(一般是稻米)在地上“畫”出若干級臺階(表示N級浮屠)。又有若干小和尚隨機地“站”在某個臺階上。最高一級臺階必須站人,其它任意。(如圖1所示)
  兩位參加遊戲的法師分別指揮某個小和尚向上走任意多級的臺階,但會被站在高級臺階上的小和尚阻擋,不能越過。兩個小和尚也不能站在同一臺階,也不能向低級臺階移動。
  兩法師輪流發出指令,最後所有小和尚必然會都擠在高段臺階,再也不能向上移動。輪到哪個法師指揮時無法繼續移動,則遊戲結束,該法師認輸。
  對於已知的臺階數和小和尚的分佈位置,請你計算先發指令的法師該如何決策才能保證勝出。
輸入格式
  輸入數據爲一行用空格分開的N個整數,表示小和尚的位置。臺階序號從1算起,所以最後一個小和尚的位置即是臺階的總數。(N<100, 臺階總數<1000)
輸出格式
  輸出爲一行用空格分開的兩個整數: A B, 表示把A位置的小和尚移動到B位置。若有多個解,輸出A值較小的解,若無解則輸出-1。
樣例輸入
1 5 9
樣例輸出
1 4
樣例輸入
1 5 8 10
樣例輸出
1 3

同上題

#include <stdio.h>
int main()
{
    int a[105],b[105],i=0,j,k,count,sum;
    char c;
    while(1)
    {
        scanf("%d%c",&a[i++],&c);
        if(c=='\n')
            break;
    }
    count=i;
    for(i = 0;i < count-1;i++)
        b[i]=a[i+1]-a[i]-1;
    b[count-1]=0;
    sum=b[0];
    for(i = 2;i < count;i = i+2)
        sum^=b[i];
    if(sum == 0)
        printf("-1\n");
    else
    {
        for(i = 0;i < count;i++)
            for(j = 1;j <= b[i];j++)
            {
                b[i] -= j;
                if(i!=0)
                    b[i-1]+=j;
                sum = b[0];
                for(k = 2;k < count;k = k+2)
                    sum ^= b[k];
                if(sum == 0)
                {
                    printf("%d %d\n",a[i],a[i]+j);
                    break;
                }
                b[i] +=j;
                if(i != 0)
                    b[i-1] -= j;
            }
    }
    return 0;
}










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章