背景:一群海盗要分一批金子。每个海盗有一个等级。现在他们要分配这批金子。
分金方法:由等级最高的海盗提出一种分配方案。然后所有的海盗投票决定是否接受分配(提议人也有一票)。并且在票数相同的情况下,提议人有决定权。如果赞成票多于等于否决票提议通过,那么海盗们按照提议分配金币。如果否决票多于赞成票,则不通过,那么提议人将被扔出船外,然后会由下一个最高等级的海盗再提出新的分配方案。
海盗的思维:海盗都是死理性派,他们的选择基于三个因素来做决定。
1,要能留在船上存活下来。
2,要使自己的利益最大化(即得到最多的金币)。
3,在其他条件相同的情况下,优先选择把别人扔出船外(这是因为每个海盗都想夺占这条船的控制权)。
问题:假定编号越大等级越高。当最高等级为n时,现在你是那个最高等级的海盗,现在有m块金子,由你提出分配方案,你该提出什么样的分配方案?
作为方案提出者,必须要使方案要通过决议,即反对者不能超过赞成者,但又想多拿金子。因此他要让支持者人数=反对者人数 - 1,加上自己的赞成票之后正好平局即可。他需要给某些人一些金子,使其能赞成,这需要抉择给谁金子能获得最高的性价比。当然是找那些在自己死后的下一轮分配方案中(想象场景)会得到最少钱的人,在那基础上加那么1粒金子。为什么只需要考虑下一轮,而不再往后考虑再下一轮呢?因为下一轮的提案者跟我自己一样不想被投死,所以不会给机会有下一轮出现的。因此只有相邻两轮有关联,纯正的数学递推。
作为普通投票者,若当前方案提出者被否决的话他之后分配会得到更加少的金子或是自己提出方案必被否决,他就投赞成票。否则,他就投反对票。
问题具体化:100个金子,5个海盗,第一个人如何提出分配方案?
假设5个人分别是1、2、3、4、5,金子分配结局是(?,?,?,?,?),X表示已死。
想象模拟“之后”的场景(其实这些场景是不存在的,因为提案者不会允许自己被投死)。由小到大递推(由未来向现在递推,有点时空穿越的意思):
1、只剩下1、2海盗时,由2号提案,很明显他已经有彻底决定权。
结局是(0,100,X,X,X)
2、 只剩下1、2、3海盗时,由3号提案,需要1个人赞成。因为下一轮1号是0金,那么给他1金就会投赞成。
结局是(1,0,99,X,X)
3、只剩下1、2、3、4海盗时,由4号提案,需要1个人赞成。因为下一轮2号是0金,那么给他1金就赞成。
结局是(0,1,0,99,X)
4、有1、2、3、4、5海盗时,由5号提案,需要2个人赞成。因为下一轮1号是0金,那么给他1金就赞成;下一轮3号是0金,那么给他1金就赞成。
结局是(1,0,1,0,98)。
对于100个金子、5个海盗的问题,这就是5号海盗会提出的分配方案:(1,0,1,0,98)。
下面的代码实现了上面的递推过程:
#include <vector> #include <iostream> #include <algorithm> using namespace std; bool numbersort(const pair<int, int> a, const pair<int, int> b) { return a.first < b.first; } bool moneysort(const pair<int, int> a, const pair<int, int> b) { return a.second < b.second; } void think(vector< pair<int, int> > &Alloc, const int n, const int money) { Alloc.push_back(make_pair(1, 0)); Alloc.push_back(make_pair(2, money)); int need; for(int i=2;i<n;i++) { need = i/2; //所需要的赞成票(不含自己) sort(Alloc.begin(), Alloc.end(), moneysort); //依据刚才的金子数排序 int cost=0; int j; for(j=0;j<need;j++) //给金子最少的need个人加1块金子,让他们投赞成票 { cost += Alloc[j].second + 1; Alloc[j] = make_pair(Alloc[j].first, Alloc[j].second+1); } for(;j<i;j++) //给其他人0金子,反正他们也投反对票 { Alloc[j] = make_pair(Alloc[j].first, 0); } Alloc.push_back(make_pair(i+1, money-cost)); //把其他的金子都给自己 } sort(Alloc.begin(), Alloc.end(), numbersort); //最后依据编号重排一下 } int main() { vector< pair<int, int> > Alloc; int n=201; int money = 100; think(Alloc, n, money); for(int i=0;i<Alloc.size();i++) cout<<"No."<<Alloc[i].first<<" : "<<Alloc[i].second<<endl; return 1; }
下面考虑极端情形,在100个金子的情况下,扩展海盗的个数,通过递推计算发现,海盗总数越多,最高等级海盗能分配给自己的金币就越少(总比被投死强啊)。
· 如果海盗总数为201个,要分配给自己0个金子才能不死。
· 如果海盗总数是202个,他也只能把这100个金币全部贿赂给其他100个海盗,而这100个海盗必须是在下一轮时得到0个金子的海盗。
· 如果海盗总数是203个,方案必定被否决,他注定被投死。
· 但是如果海盗总数是204,会有神奇的变化,因为到下一轮的提案必定被否决,所以203号海盗一定会是当前的支持者。所以204可以靠他自己的一票+203的一票+另外100个海盗给1个金子,正好平局。
。。。。。。
留作思考。。。
这是一个递推的绝佳模型。每一次的抉择只直接依赖于较小一轮的结果。