跳转至

5 个海盗分 100 枚金币

题

5 个海盗 A > B > C > D > E(按资历排序)分 100 枚金币。规则:

  • 最资深的活人提出分配方案,全体(含提议者)投票;
  • 赞成票至少占一半(含提议者本人)则通过,按方案分;否则提议者被扔下海,由下一位资深者重新提议。

每个海盗都绝对理性,偏好依次为:先保命,再多拿金币,两者都一样时宁愿把提议者扔下海。金币不可分割。A 该怎么提?

一句话

A 拿 98,C 和 E 各拿 1,B 和 D 拿 0——方案里没有一处是"公平",全靠逆向归纳:每个人只比较"这个方案"和"提议者死后我能拿多少"。

关键技巧

从最小的局倒着推。 每个人投票时拿当前方案和"提议者被扔下海后的那局"比。所以只要知道 \(k-1\) 人局的结果,就能算出 \(k\) 人局:

  • 在 \(k-1\) 人局里拿 \(x\) 枚的人,现在给他 \(x+1\) 枚就能买到他的票(给 \(x\) 枚他会投反对——无差别时宁愿杀人)。
  • 提议者要凑够票数,就去买最便宜的票:那些在下一局里一无所获的人,每票 1 枚。

这是一种博弈里的逆向归纳(backward induction),和秘书问题、掷骰子重投这类最优停止问题同一个思路:当下的决策只取决于"之后会怎样"的精确值。

解

记"剩几个人"为局面。需要的赞成票为 \(\lceil k/2 \rceil\)(含提议者)。

局面 提议者 需要票数 方案(按资历从高到低)
D, E D 1 D 100, E 0
C, D, E C 2 C 99, D 0, E 1
B, C, D, E B 2 B 99, C 0, D 1, E 0
A, B, C, D, E A 3 A 98, B 0, C 1, D 0, E 1

逐行看:

  • 2 人:D 自己一票就是一半,直接全拿。E 无能为力。
  • 3 人:C 需要 2 票。D 在下一局拿 100,买不起;E 在下一局拿 0,给 1 枚就买到。
  • 4 人:B 需要 2 票。下一局里 D 拿 0,给 D 1 枚。
  • 5 人:A 需要 3 票,自己一票,还要买 2 票。下一局里拿 0 的是 C 和 E,各给 1 枚。

每一步要买的票数恰好等于下一局的"零收入者"人数,所以每一局的方案都是唯一的。

延伸

  • 规则改成严格过半:2 人局时 D 需要 2 票,E 一定反对,D 必死——于是在 3 人局里 D 会无条件支持 C,C 拿 100。往上推:4 人局 B 需 3 票,买 D、E 各 1 枚,方案 \(98, 0, 1, 1\);5 人局 A 需 3 票,买 C(1 枚)再加 D 或 E 之一(各要 2 枚),方案 \(97, 0, 1, 2, 0\) 或 \(97, 0, 1, 0, 2\),不再唯一。可见"至少一半"还是"严格过半"一字之差,答案整个变了。
  • 海盗更多时(至少一半规则):\(n\) 人局的提议者拿 \(100 - \lfloor (n-1)/2 \rfloor\),给与自己同奇偶编号的其余人各 1 枚。\(n = 199\)、\(200\) 时提议者只剩 1 枚;\(n = 201\) 时 100 枚全得送出去,自己分文不取也能活。
  • 超过 201 人(Stewart, 1999):金币不够买票了,关键变成"必死者的票"。\(n=203\) 的提议者只有自己一票加 100 枚买来的票,差一票,必死;\(n = 204\) 时 203 号为了不死会无条件支持他,刚好够票。继续推,能活的提议者恰好是

    \[ n = 200 + 2^k: \quad 201, 202, 204, 208, 216, 232, 264, 328, 456, \ldots \]

    两个幸存编号之间的那些人全部必死——他们的票是下一个幸存者的"免费票",间隔每次翻倍。 - 常见错误一:以为 A 要讨好 B。B 在下一局拿 99,买他的票最贵;该买的是下一局里最穷的人。 - 常见错误二:忘了写"无差别时投反对"。若无差别时投赞成,每个人收 0 枚也肯投票,逐级方案都会变——题目的偏好顺序必须说全。

数值验证

逆向归纳直接编程:每局让提议者按价格从低到高收买票,检查唯一性,再扫描到 600 人找幸存编号。

import math

def solve(n, G, need):
    """海盗编号 1..m,m 最资深、先提议。need(m) = m 人局通过所需票数(含提议者)。
    偏好:先保命,再多拿金币,无差别时投反对。plan[m] = 分配 dict,None 表示提议者必死。"""
    plan = {1: {1: G}}
    for m in range(2, n + 1):
        k = m - 1
        while plan[k] is None:             # m 死后,往下第一个能活的局面
            k -= 1
        base = plan[k]
        votes = 1 + (m - 1) - k            # 自己 + 夹在中间的必死者(白给的票)
        short = need(m) - votes
        if short <= 0:
            plan[m] = {m: G}; continue
        prices = sorted((base.get(i, 0) + 1, i) for i in range(1, k + 1))
        cost = sum(p for p, _ in prices[:short])
        if cost > G:
            plan[m] = None; continue
        alloc = {i: p for p, i in prices[:short]}
        alloc[m] = G - cost
        alloc['_unique'] = short == len(prices) or prices[short - 1][0] < prices[short][0]
        plan[m] = alloc
    return plan

half = lambda m: math.ceil(m / 2)          # 至少一半(含提议者)
strict = lambda m: m // 2 + 1              # 严格过半

for rule, f in (("至少一半", half), ("严格过半", strict)):
    p = solve(5, 100, f)
    for m in range(2, 6):
        a = p[m]
        if a is None:
            print(rule, m, "提议者必死"); continue
        print(rule, m, [a.get(i, 0) for i in range(m, 0, -1)], "唯一" if a.get('_unique', True) else "不唯一")
# 至少一半 2 [100, 0] 唯一
# 至少一半 3 [99, 0, 1] 唯一
# 至少一半 4 [99, 0, 1, 0] 唯一
# 至少一半 5 [98, 0, 1, 0, 1] 唯一
# 严格过半 2 提议者必死
# 严格过半 3 [100, 0, 0] 唯一
# 严格过半 4 [98, 0, 1, 1] 唯一
# 严格过半 5 [97, 0, 1, 0, 2] 不唯一

p = solve(600, 100, half)
assert all(p[m][m] == 100 - (m - 1) // 2 for m in range(1, 202))
for m in range(2, 201):                    # 方案唯一,且恰好给同奇偶的人各 1 枚
    a = p[m]
    assert a.get('_unique', True)
    assert sorted(i for i in a if i not in ('_unique', m) and a[i] > 0) == \
           [i for i in range(1, m) if i % 2 == m % 2]
print([p[m][m] for m in (5, 10, 50, 199, 200, 201, 202)])   # [98, 96, 76, 1, 1, 0, 0]
print([m for m in range(201, 601) if p[m] is not None])
# [201, 202, 204, 208, 216, 232, 264, 328, 456]

200 人以上的部分,代码按一个确定的方案往下推,只用来数票数够不够。这一段谁拿金币不再唯一——提议者可以在多个同价候选人里任选 100 人——Stewart 原文专门处理了这种不确定性,结论同样是幸存编号 \(200 + 2^k\)。