海盗分赃难题:
十个海盗要瓜分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语言源代码:
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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;
}