您的位置:首页 > 其它

海盗分金

2013-11-12 17:44 225 查看
海盗分赃难题:

  十个海盗要瓜分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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: