博弈學習(三) HDU 1538 A Puzzle for Pirates(經典的海盜分金推理)

bigbigship發表於2015-04-01


轉載請註明出處,謝謝 http://blog.csdn.net/ACM_cxlove?viewmode=contents           by---cxlove

題目:這是一個經典問題,有n個海盜,分m塊金子,其中他們會按一定的順序提出自己的分配方案,如果50%以上的人贊成,則方案通過,開始分金子,如果不通過,則把提出方案的扔到海里,下一個人繼續。

http://acm.hdu.edu.cn/showproblem.php?pid=1538

首先我們講一下海盜分金決策的三個標準:保命,拿更多的金子,殺人,優先順序是遞減的。

同時分為兩個狀態穩定狀態和不穩定狀態:如果當n和m的組合使得最先決策的人(編號為n)不會被丟下海, 即遊戲會立即結束, 就稱這個狀態時"穩定的". 反之, 問題會退化為n-1和m的組合, 直到達到一個穩定狀態, 所以乘這種狀態為"不穩定的".

接下來我們從簡單的開始分析:
如果只有兩個人的話:那麼2號開始提出方案,這時候知道不管提什麼,他自己肯定贊成,過半數,方案通過,那麼2號肯定把所有的金子都給了自己。
如果只有三個人的話:那麼3號知道,如果自己死了,那麼2號肯定能把所有金子拿下,對於1號來說沒有半點好處。
那麼他就拿出金子賄賂1號,1號拿到1個金子,總比沒有好,肯定贊成3號,剩下的3號拿下。
如果只有四個人的話:那麼4號知道,如果自己死了,那麼1號拿到1個金子,2號什麼都沒有,3號拿下剩下的金子。
那他就可以拿出部分金子賄賂2號,2號知道如果4號死了,自己將什麼都沒有,他肯定贊成4號。
如此類推下去,貌似就是第一個決策的時候,與他奇偶性相同的人會被賄賂拿到1個金子,剩下的全歸提出方案的人所有。

但是會有一個問題便是,如果金子不夠賄賂怎麼辦。
情況1、我們首先歸納之前的,如果n<=2*m時候,前面與n相同奇偶性的得到1個金子,剩下的第n個人拿下。
情況2、如果n==2*m+1,第n個人拿出m個金子賄賂前面的m個人。自己不拿金子,這樣剛好保證自己不死,這就是之前提到的優先順序,首先得保命,如果自己拿了一個金子,那麼前面就有一個人會反對,因為對於那個人,不管怎麼樣都分不到金子,則輪到第三個原則,殺人,肯定投反對票。

剩下來我們考慮,錢不夠賄賂的情況:
我們將問題具體化:如果有500個海盜,只有100個金子,那麼前面201個已經分析過了。
對於202號來說,自己不能拿金幣,而賄賂上一輪沒有拿到金幣的101人中的100人就夠了。
對於203號來說,需要102個人的支援,顯然加上他自己,還需要101票,而金子不夠賄賂,別人會反對,而達到殺人的目的。
對於204號來說,他知道一旦自己死了,203號是必死,抓住這點,203必然支援他,因為203號寧可不要金幣,也要保住性命,所以204號把100個金幣分給之前的100個人,然後203和他自己的兩票保證自己不死。
對於205號來說,203,和204是不會支援他的,因為一旦205死了,他們不僅可以保住性命,而且還可以看著205死掉。所以205是必死
那麼206呢,雖然205必死,會支援他,但是還是缺一票,所以必死。
對於207呢,205和206之前是必死,會支援他,但是加上自己以及100個賄賂名額,還是必死
對於208號,205,206.,207因為後面是必死的,肯定會支援208成功,那麼208剛好能湊齊104票,得以保命。

以下我們猜想:n=2*m+2^k的情況下,是可以保命的,稱為穩定狀態,否則為不穩定狀態,我們證明一下:
首先對於n來說,有m票賄賂,但是對於2*m+2^(k-1)以前必死的死,他們會支援2*m+2^(k-1),因為他們肯定拿不到錢,而且支援2*m+2^(k-1),另外根據殺人原則,希望之後的人都死,輪到2*m+2^(k-1)決策的時候保命就行了。
同理2*m+2^(k-1)到2*m+2^k之間的2^(k-1)-1個人來說,他們必死,所以必定支援2*m+2^k,加上m個金幣賄賂的,加上他自己,剛好有m+2^(k-1)。這樣剛好湊齊一半,可以不死。
證明完畢:2*m+2^k的人可以保命,否則必死。

我們考慮一下分金幣情況:
情況3:對於第2*m+2^k個人來說,他可以保命,肯定分不到金子,而他手上的m個金子,可以賄賂m個人,但是具體是哪些人是不定的。則不管是不能分到金子,還是可能分不到金子的人來說,結果都為0。
情況4:對於2*m+2^(k-1)到2*m+2^k之間的來說,他們的決策是必死,而在他們決策的時候,其它人分得金幣情況也為0。

我們來解釋一下金幣的不確定性:

金幣數量的不確定性:由上面的推理可知, 當n=2m+2時, 上一輪推理沒有分到金幣的人的金幣數量首次具有不確定性, 並且在n>2m+2時, 這種不確定性一定會延續下去, 輪到因為n號決策者之前的一個人決策時, 那個人肯定分不到金幣了, 所以在上一輪推理中沒有分到金幣的人的個數一定大於m.

綜合情況1,2,3,4便是本題的解,
程式碼如下:
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<ctime>  
  4. #include<cstring>  
  5. #include<cmath>  
  6. #include<algorithm>  
  7. #include<cstdlib>  
  8. #include<vector>  
  9. #define C    240  
  10. #define TIME 10  
  11. #define inf 1<<25  
  12. #define LL long long  
  13. using namespace std;  
  14. //儲存2的冪  
  15. int fac[15]={2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};  
  16. void slove(int n,int m,int p){  
  17.     //金幣夠賄賂的情況  
  18.     if(n<=2*m){  
  19.         //不是決策者,而且奇偶性相同,都能被賄賂  
  20.         if(n!=p&&(n%2==p%2))  
  21.             printf("1\n");  
  22.         //剩下的都是決策者擁有  
  23.         else if(n==p)  
  24.             printf("%d\n",m-(n-1)/2);  
  25.         else  
  26.         //其餘人分不到金幣,他們的決策不影響全域性  
  27.             printf("0\n");  
  28.         return ;  
  29.     }  
  30.     //這時候的不同在於決策者不能拿金幣  
  31.     else if(n==2*m+1){  
  32.         if(p<2*m&&p&1)  
  33.             printf("1\n");  
  34.         else  
  35.             printf("0\n");  
  36.         return ;  
  37.     }  
  38.     int t=n-2*m,i;  
  39.     //這是剛好保命的情況,對於決策者來說,肯定沒有金幣  
  40.     //對於其它人來說,要麼肯定沒有金幣,要麼可能沒有金幣,不確定性  
  41.     for( i=0;i<14;i++){  
  42.         if(t==fac[i]){  
  43.             printf("0\n");  
  44.             return;  
  45.         }  
  46.     }  
  47.     for( i=1;i<14;i++)  
  48.         if(t<fac[i]){  
  49.             //決策者必死  
  50.             if(p>2*m+fac[i-1]&&p<2*m+fac[i])  
  51.                  printf("Thrown\n");  
  52.             else  
  53.                  printf("0\n");  
  54.             return ;  
  55.         }  
  56. }  
  57. int main(){  
  58.     int t,n,m,p;  
  59.     scanf("%d",&t);  
  60.     while(t--){  
  61.         scanf("%d%d%d",&n,&m,&p);  
  62.         slove(n,m,p);  
  63.     }  
  64.     return 0;  
  65. }  
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<vector>
#define C    240
#define TIME 10
#define inf 1<<25
#define LL long long
using namespace std;
//儲存2的冪
int fac[15]={2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};
void slove(int n,int m,int p){
    //金幣夠賄賂的情況
    if(n<=2*m){
        //不是決策者,而且奇偶性相同,都能被賄賂
        if(n!=p&&(n%2==p%2))
            printf("1\n");
        //剩下的都是決策者擁有
        else if(n==p)
            printf("%d\n",m-(n-1)/2);
        else
        //其餘人分不到金幣,他們的決策不影響全域性
            printf("0\n");
        return ;
    }
    //這時候的不同在於決策者不能拿金幣
    else if(n==2*m+1){
        if(p<2*m&&p&1)
            printf("1\n");
        else
            printf("0\n");
        return ;
    }
    int t=n-2*m,i;
    //這是剛好保命的情況,對於決策者來說,肯定沒有金幣
    //對於其它人來說,要麼肯定沒有金幣,要麼可能沒有金幣,不確定性
    for( i=0;i<14;i++){
        if(t==fac[i]){
            printf("0\n");
            return;
        }
    }
    for( i=1;i<14;i++)
        if(t<fac[i]){
            //決策者必死
            if(p>2*m+fac[i-1]&&p<2*m+fac[i])
                 printf("Thrown\n");
            else
                 printf("0\n");
            return ;
        }
}
int main(){
    int t,n,m,p;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&n,&m,&p);
        slove(n,m,p);
    }
    return 0;
}

相關文章