作业评分:每题 10 分,选做属于加分题

一些共性的问题

  • 作业只收到 41 份,名单有 54 位同学,请确认自己已经提交作业
  • 作业严禁抄袭
  • 作业记得写好学号姓名,并用订书钉或回形针等方式装订好
  • 许多同学表示不会写伪代码
    • 作业都只需要给出伪代码,理论上不应该使用任何一种实际的编程语言书写(如 java/c/python)很多同学作业中伪代码直接交了可运行的代码,这样书写很费时间,同时也没法很好展示自己的思路
    • 写伪代码主要在于展现算法思路,只需要保留核心算法部分即可。一些辅助函数在说明了功能后可以直接使用,没必要完整的写出来。这里放一篇 pseudo code 的参考文章
    • 大家可以看一下教程上给的代码示例,如 4.8 Heapsort 这一章的伪代码

海盗分金

上交作业的同学必做部分(m=5)基本都得分了。泛化的情况属于选做部分,很多同学都给出了思路,主要失分点在于没有考虑到"乐观"这一条件

以下给一个 python 参考代码

def solve(m, n):
    """解决海盗分金问题,时间复杂度 O(m^2lgm);使用一些技巧可优化到 O(m^2)

    Args:
        m (int): 海盗数
        n (int): 金币数

    Returns:
        (list): 由第一个人分配,每个人期望分到的金币数,-1 表示无法存活
    """

    # 临界情况
    if m == 1:
        return [n]
    # 递归:规约问题到 m-1 个海盗
    plan = solve(m-1, n)

    # 按照 m-1 个海盗时每个人期望获得的金币数从小到大排序
    sorted_plan = sorted(zip(plan, list(range(m-1))))

    current_plan = [0] * m
    remain_money = n
    # 第一个海盗需要在剩下 m 个海盗中争取到 m/2(下取整)个同意票
    for i in range(m//2):
        expected_money, member_id = sorted_plan[i]
        expected_money += 1  # 嗜血:需要多给一个金币赢取同意
        remain_money -= expected_money
        # 如果金币不够分了,则无法存活
        if remain_money < 0:
            return plan + [-1]
        else:
            current_plan[member_id] = expected_money
    # 第一个海盗可以获得剩余所有金币
    # 注意:到目前为止,去除 -1,current_plan 之和为 n
    # 此时得到的是一个可行的分配方案
    current_plan[-1] = remain_money

    # 乐观:对于最后需要赢得同意的海盗,可能只需在多人中选部分即可
    # 但所有持相同期望金币的都认为自己会被选到,在此要更新他们认为的期望金币
    # n >= 6 时乐观起作用
    while i+1 < m and sorted_plan[i][0] == sorted_plan[i+1][0]:
        i += 1
        expected_money, member_id = sorted_plan[i]
        current_plan[member_id] = expected_money + 1

    return current_plan

扔鸡蛋

必做题

为了计算 $f(k,m)$,假设在第 $i$ 层楼扔一个蛋。如果碎了,我们还剩 $k-1$ 个蛋,强度肯定小于 $i$,故问题化为 $f(k-1, i)$;如果没碎,把第 $i$ 层楼看做是新的一楼,则问题化为 $f(k, m-i)$。最坏情况是两者的最大值,为了取得最优,我们选择最佳的第 $i$ 层。

故递归为 $f(k,m)=\min{(\max{f(k-1,i),f(k,m-i)})+1|1\leq i\leq m}$

部分同学没有避免重复计算。可以把算过的值放在二维数组中保存起来,遇到相同计算时直接使用数组中的值。该方法时间复杂度 $O(n^3)$

选做

可参考文章:从《鹰蛋》一题浅析对动态规划算法的优化

选做部分如果没有解释或解释不清也适当扣分。很多同学看了题解做题,希望至少把题解看懂了再写上来