海盗分赃难题:
十个海盗要瓜分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号的腰包了。
//定义排序的结构体,用于寻找可以用最少分配而拉拢的海盗
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;
}