关闭

海盗分金

1421人阅读 评论(0) 收藏 举报
 

海盗分赃难题:

  十个海盗要瓜分100枚币,为此他们拟定了以下规则。

  从船长到厨子每个海盗由高到低共分十个等级,分配权在最高等级的海盗手里。他可以任意分配每个海盗的所得,但必须取得一半或一半以上海盗(包括自己在内)的支持,否则他将被同伴处死。处死之后分配权将转移到下一个等级最高的海盗手里,当然,他也将面临同样艰难的选择。

  基于海盗们贪婪而凶残的本性,每个没有分配权的海盗都想分得更多的币和处死自己的上级。但相对后者而言,多分得一枚币也许更有吸引力。我们假定所有的海盗都是深思熟虑的老手,他们能精确地计算自己未来的得失,从而根据利益最大原则支持或反对上级的分配。

  问:作为一号海盗的船长有什么办法为自己分得最多的币而不被处决?

 

问题的背景:

  “海盗分赃”算是一道比较经典的智力测试题,也是我所见过的最有挑战性的一道。据说你能在半小时内解开说明你很了不起,至少不会输给那些“深思熟虑的老手”。对啦,我的意思是说所有的海盗必须是知道答案的,否则本题的假设不能成立,我最欣赏这一点。

 

解题的关键:

  解题的关键在于反向推理。

  假设只剩下九号和十号海盗,按规则应该由九号海盗分配。猜会怎么着?九号一定会把100枚币统统据为己有,因为这时已经没有任何力量可以阻止他这么做了,他给自己投一票就能达到50%的支持率。

  由此,“深思熟虑”的十号海盗一定会明白处死八号自己最好的结果也将是一无所得。

  现在假定由八号来分赃,十号一定会这么想:“他至少应该分给我一枚枚,否则我一定投票弄死他。”

  “深思熟虑”的八号也一定不难猜到十号的心理动向,因为根据推理,十号的打算是必然的结果。于是他决定用最小的代价——1枚币——去贿赂十号海盗。如此,加上他自己的支持,2比1,他肯定死不了!所以九号海盗一毛钱也别想捞。

  按照上面的思路可以一直逆推回一号海盗,船长可以根据船员们的心理作出对自己最有利的分配方案。

 

递归的作用:

  如果要求我们用程序来模拟这一过程该怎么办?

  递归。对!大多数训练有素的程序员一定会先想到递归。因为它实在就是递归类问题的典范。

  按照我们的分析过程,我把伪代码写在下面:

/*

 *DivideSpoils为递归函数

 *参数num为参与分赃的海盗数

 *分赃成功函数则返回分赃结果,否则函数返回空值

 */

DivideSpoils (num)

    if num=2 then

        rst[0]=100

        rst[1]=0

        return rst

    endif

 

    rst=DivideSpoils(num-1)

    if rst=空值 then

        return 空值

    else

        /*重新分配,使至少一半的海盗感到满意*/

        rst=Redivide(rst,num-1)

        return rst

    endif

 

end

 

/*

 *Redivide负责对币重新分配,使至少一半的海盗感到满意

 *oldrst为num个海盗的分配结果

 *分配成功则返回num+1个海盗的分配结果,否则函数返回空值

 */

Redivide (oldrst,num)

    rst := 1X(num+1)维数组

    s := 1Xnum维数组

    half=[num/2]  //[x]为不大于num/2的最大整数

 

    /*对oldrst从小到大排列,只需排出前第half位,排列后的下标存入s中*/

    s[i]={k, oldrst[s[k-1]]<=oldrst[k]} when i<half

    s[i]=i when i>=half

 

    /*贿赂一半的海盗,使他们能多分一枚币*/

    rst[s[i]+1]=oldrst[s[i]]+1 when i<half

    /*余下的另一半一分钱不给*/

    rst[s[i]+1]=0 when i>=half

    /*余下币则归自己*/

    rst[0]=余下

 

    if rst[0]<0 then  //rst[0]<0说明没有足够币贿赂部下,分配失败

        return 空值

    else

        return rst

    endif

 

end

  

由以上代码来看,递归的停止条件来自我们上面基于两个海盗分赃结果的假设。我们似乎可以进一步简化它:

if num=1 then

    rst[0]=100

    return rst

endif

这样做程序也能工作,并得到了正确答案。可想见递归是门很灵活的艺术。

 

问题的扩展:

  分赃问题很简单是不是?不简单!还记得三个海盗分赃时我们准确分析了十号的心理么?是不是还遗漏什么了?是,九号怎么想的我们完全没有去考虑,这种粗暴的态度可能会给分配者带来无法预料的灾难!

  怎么说?我们不妨设想当九号推断出自己将受到不公正待遇的时候,他可能会私下跟十号协商。他可能信誓旦旦地保证:“嘿,兄弟,我们把八号给做了,剩下的币四六分怎么样?” 尽管他在履行承诺后的所得要低于自己的部下,但这比起空手而归却要好得很多,况且他们还结果了一个上级。

  八号在得知这种情况后一定很恐慌,因为他已经完全不能决定自己的命运了。他必须尽最大可能去贿赂十号,60币?70?100?!!!他惊恐地睁大了眼睛,他怎么会知道九号承诺了多少,也许是100币?九号认定要取自己性命了?!

  我们可以想像这时十号肯定颇为满意,他似乎惟一要做的就是不动声色,看谁出的价位更能打动他。其实不然,因为八号可能会转而与九号密谈,比如说之前九号承诺干掉八号以后自己只拿40,现在八号承诺给九号41,九号就会反过到拥护8号...

  到此为止海盗分问题就已经不再是单纯的智力测试了,而应该配得上另一个更体面的名字:博弈论。

  最后海盗们会不会达成某个满意的协议,我现在还不知道,这个问题打发给以后的空闲时间。

 

c语言源代码:

View Code

#include<stdio.h>
#include<stdlib.h>

const int COINS=100;
const int PIRATES=10;

int* divi_spoils(int);
int* redivide(int*,int);
int* rank(int*,int,int);

int main(void){
    int i;
    int* rst;

    rst=divi_spoils(PIRATES);
    if(rst){
        for(i=0;i<PIRATES;i++)
            printf("Pirate<%d>: %d coins\n",i+1,rst[i]);
    }
    else{
        printf("Be Executed!\n");
    }

    free(rst);
    return 0;
}

int* divi_spoils(int total){
    int* rst=NULL;

    if(total==1){
        rst=(int*)malloc(total*sizeof(int));
        rst[0]=COINS;
        return rst;
    }

    rst=divi_spoils(total-1);
    if(rst==NULL)
        return NULL;
    else{
        rst=redivide(rst,total-1);
        return rst;
    }
}

int* redivide(int* old_rst, int n){
    int i,left=COINS,half=n/2;
    int* s,* rst;

    s=rank(old_rst,n,half);
    rst=(int*)malloc((n+1)*sizeof(int));
    for(i=0;i<half;i++){
        rst[s[i]+1]=old_rst[s[i]]+1;
        left-=rst[s[i]+1];
    }
    for(i=half;i<n;i++)
        rst[s[i]+1]=0;
    free(s);
    free(old_rst);

    if(left<0){
        free(rst);
        return NULL;
    }
    else{
        rst[0]=left;
        return rst;
    }
}

int* rank(int* data, int n, int len){
    int i,j,tmp;
    int* s;

    s=(int*)malloc(n*sizeof(int));
    for(i=0;i<n;i++)
        s[i]=i;

    for(i=0;i<len;i++)
    for(j=n-1;j>i;j--)
        if(data[s[j]]<data[s[j-1]]){
            tmp=s[j];
            s[j]=s[j-1];
            s[j-1]=tmp;
        }
           
    return s;
}

 

 

 

海盗分金编程代码

一、    博弈论
海盗分金的故事
5个海盗抢到了100个金币,每一颗都一样的大小和价值连城。
他们决定这么分:
1。抽签决定自己的号码(1,2,3,4,5)
2。首先,由1号提出分配方案,然后大家5人进行表决,当且仅当半数和超过半数的人同意时,按照他的提案进行分配,否则将被扔入大海喂鲨鱼。
3。如果1号死后,再由2号提出分配方案,然后大家4人进行表决,当且仅当半数和超过半数的人同意时,按照他的提案进行分配,否则将被扔入大海喂鲨鱼。
4。依次类推......
问题:第一个海盗提出怎样的分配方案才能够使自己的收益最大化
条件:每个海盗都是很聪明的人,如果前面的人提出的方案对自己没好处肯定会否决,如果好处比后面持续下去的方案好就投票。

二、题目
1.给出5个海盗分配100个金币的算法、过程和分析。
2. 改变一下规则,投票中方案必须得到超过50%的票数(只得到50%票数的方案的提出者也会被丢到海里去喂鱼),那么如何解决5个海盗分100枚金币的问题?
3. 不改变规则,如果让100个海盗分100枚金币,会发生什么?
4. 如果每个海盗都有1枚金币的储蓄,他可以把这枚金币用在分配方案中,如果他被丢到海里去喂鱼,那么他的储蓄将被并在要分配的金币堆中,这时候又怎样?
三、要求
1.题目1.2必须解答出来
2.题目3.4随意

二、    此题公认的标准答案是:1号海盗分给3号1枚金币,4号或5号2枚金币,自己则独得97枚金币,即分配方案为(97,0,1,2,0)或(97,0,1,0,2)。现来看如下各人的理性分析:

   首先从5号海盗开始,因为他是最安全的,没有被扔下大海的风险,因此他的策略也最为简单,即最好前面的人全都死光光,那么他就可以独得这100枚金币了。
   接下来看4号,他的生存机会完全取决于前面还有人存活着,因为如果1号到3号的海盗全都喂了鲨鱼,那么在只剩4号与5号的情况下,不管4号提出怎样的分配方案,5号一定都会投反对票来让4号去喂鲨鱼,以独吞全部的金币。哪怕4号为了保命而讨好5号,提出(0,100)这样的方案让5号独占金币,但是5号还有可能觉得留着4号有危险,而投票反对以让其喂鲨鱼。因此理性的4号是不应该冒这样的风险,把存活的希望寄托在5号的随机选择上的,他惟有支持3号才能绝对保证自身的性命。
   再来看3号,他经过上述的逻辑推理之后,就会提出(100,0,0)这样的分配方案,因为他知道4号哪怕一无所获,也还是会无条件的支持他而投赞成票的,那么再加上自己的1票就可以使他稳获这100金币了。
   但是,2号也经过推理得知了3号的分配方案,那么他就会提出(98,0,1,1)的方案。因为这个方案相对于3号的分配方案,4号和5号至少可以获得1枚金币,理性的4号和5号自然会觉得此方案对他们来说更有利而支持2号,不希望2号出局而由3号来进行分配。这样,2号就可以屁颠屁颠的拿走98枚金币了。

   不幸的是,1号海盗更不是省油的灯,经过一番推理之后也洞悉了2号的分配方案。他将采取的策略是放弃2号,而给3号1枚金币,同时给4号或5号2枚金币,即提出(97,0,1,2,0)或(97,0,1,0,2)的分配方案。由于1号的分配方案对于3号与4号或5号来说,相比2号的方案可以获得更多的利益,那么他们将会投票支持1号,再加上1号自身的1票,97枚金币就可轻松落入1号的腰包了。

要用C语言编写

//定义排序的结构体,用于寻找可以用最少分配而拉拢的海盗
typedef struct{       
int key;
int index;
}NodeType;

#include "stdio.h"
#include "math.h"
//最大海盗数
#define MAX_Pirate 100
//海盗分配序列(倒叙存放,便于循环插入修改)
int allot[MAX_Pirate+1];
//临时数组,用于排序
NodeType temp[MAX_Pirate+1];

//快速排序定位函数
int Partition(NodeType r[],int low,int high){
r[0]=r[low];
int pivotkey=r[low].key;
while(low<high){
   while(low<high && r[high].key>=pivotkey)
    --high;
   r[low]=r[high];
   while(low<high && r[low].key<=pivotkey)
    ++low;
   r[high]=r[low];
}
r[low]=r[0];
return low;
}

//快速排序函数
void QSort(NodeType r[],int low,int high){
if(low<high){
   int pivotloc=Partition(r,low,high);
   QSort(r,low,pivotloc-1);
   QSort(r,pivotloc+1,high);
}
}

//海盗分配序列的排序函数,排序结果存入temp
void QSort_allot(int allot[],int n){
for(int i=1;i<=n;i++){
   temp[i].key=allot[i];
   temp[i].index=i;
}
QSort(temp,1,n);
}

//判定函数,用于判定编号为index的海盗是否是最优拉拢对象
bool num_in_temp_index(int index,NodeType temp[],int n){
for(int i=1;i<=n;i++)
   if(index==temp[i].index)
    return true;
return false;
}


//海盗分金函数
int pirate_gold(int allot[],int gold_num,int pirate_num){
allot[1]=0;allot[2]=0;allot[3]=gold_num-allot[1]-allot[2];
if(pirate_num==3)
   return 1;
else{
   for(int i=4;i<=pirate_num;i++){
    int k=i/2;
    QSort_allot(allot,i-2);
  
    for(int j=1;j<=i-2;j++){
     if(!num_in_temp_index(j,temp,k))
      allot[j]=0;
     else
      allot[j]++;
    }
    allot[i-1]=0;
    allot[i]=gold_num;
    for(int x=1;x<=i-1;x++)
     allot[i]=allot[i]-allot[x];
 
   }
}
}

//输出函数
void print(int allot[],int pirate_num)
{
printf("\n第一个海盗最多得到的金币数为:%d\n最优分配策略为:( ",allot[pirate_num]);
for(int i=pirate_num;i>1;i--)
   printf("%d ,",allot[i]);
printf("%d )\n\n",allot[1]);
}


void main(){
int pirate_num,gold_num;
    printf("输入金币数量和海盗数量(形如:100 5):");
scanf("%d%d",&gold_num,&pirate_num);

pirate_gold(allot,gold_num,pirate_num);
print(allot,pirate_num);
return;
}

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:650994次
    • 积分:8947
    • 等级:
    • 排名:第2090名
    • 原创:264篇
    • 转载:230篇
    • 译文:1篇
    • 评论:61条
    最新评论