推广的海盗分金币问题的算法

来源:互联网 发布:linux怎么启动jenkins 编辑:程序博客网 时间:2024/04/29 04:21

在杨军blog上看见一个海盗分金币问题,很有趣,准备在这里推广并用计算机可操作的算法实现。原题的描述以及解题思路杨军已经给出了,详见hi.baidu.com/yjpro/blog/category/%D6%C7%C1%A6%D1%B5%C1%B7。

推广:
n个海盗,m个金币。其中n >= 2,m >= 1。
基本思想:
杨军已经给出,关键就是“倒着想”,并假设每个海盗都很理智。每个海盗都有一个金币数量的收入的期望,如果他必死,那么他的收入期望是-1。如果第k个海盗来制定分配方案(因为是倒着想,所以不考虑前k-1个海盗),那么他需要贿赂他后面n-k个海盗中不少于一半的海盗,使得他们给自己投赞成票。为了使自己的收入最多,他贿赂的方式就是从期望收入最少的海盗开始,在他原来的期望收入基础上再加1个金币,直到贿赂了足够多的海盗。这样描述不准确,但是大体思想说出来了。
算法:
利用数学归纳法。初始化时,将海盗顺序标号为1~n。将第n个海盗的期望收入置为m,第n-1个海盗的期望收入置为-1。假设后k个海盗的收入期望已经算出来,那么倒数第k+1个海盗的收入期望这样计算:将后k个海盗按收入期望升序排序,选择前(k+1)/2个期望,每个期望加1,用m减去这些期望的和,得出的就是倒数第k+1个海盗的收入期望。如果该期望小于0(证明总金币量不够他贿赂足够的人,他将死去,因为我们假设海盗是一群糙人,他们不愿意看见决策者没有给自己比期望更多的贿赂:)),那么置该海盗的收入期望为-1;否则,该海盗贿赂成功,被选出的(k+1)/2个海盗每个人的期望收入都增加1,其余k-(k+1)/2个海盗的期望收入变为0。继续迭代。

程序伪码:
数据结构<id, e>,其中id表示海盗的id值,e表示其期望收入。
L:线性表,最好为链表,存储海盗节点<id, e>。L中第一个元素是L[1]。
Insert(L, <id, e>):L上的操作,将<id, e>插入到L“适当的”位置。何为“适当”?稍后解释。
Sort(L):将L中的海盗节点按照e值升序排序。

main:
initial:
     Insert(L, <n, m>);
     Insert(L, <n-1, -1>);
begin:
     for i = [3 to n]   //倒着来
            e = 0;
            for k = [1 to i/2]
                   e += (L[k].e + 1);
            endfor;
            if e > m   //不够贿赂,这个海盗死定了!
                   e = m+1;
            else
                   for k = [1 to i/2]  //开始分赃了
                            L[k].e++;
                   for k = [i/2+1 to n]   //这些海盗因为倒数第i个海盗的存在而期望归0
                            L[k].e = 0;
            endif;
            Insert(L, <n-i+1, m-e>);   //将倒数第i个海盗入列
            Sort(L);
     endfor

     return i, L(i).e = max(L(j).e, j = [1 to n] );
end

解释:
这里引入Sort操作,完全是为了叙述方便而进行的抽象。因为,由于算法本身的特点,如果每次更新L中相关节点的e值后,将e值为0的节点cut并粘贴到 L的前面,那么形成的新L是升序排列的。再选择适当的位置将新海盗插入到L中。所以,每次更新后用sort排序,如果n很大,将是一种很大的时间开销。

原创粉丝点击