美文网首页
算法思维训练趣味任务(十四) 适龄高中阶段

算法思维训练趣味任务(十四) 适龄高中阶段

作者: Python_Camp | 来源:发表于2022-05-28 15:47 被阅读0次

    在金盆洗手游戏中,有两个玩家,A和B,金盆排列成一排,每个金盆里都有一些金币。玩家可以看到每个金罐里有多少金币,每个玩家可以交替轮换,玩家可以从队伍的任何一端挑选一个金罐。最后拥有较多金币的玩家就是赢家。目标是 "最大化 "A收集的金币数量,假设B也是 "最佳 "发挥,A开始游戏。

    比如说 For example

                          Player    Opponent
     
    4, 6, 2, 3              3
    4, 6, 2                            4
    6, 2                    6
    2                                  2
    
                         9 coins    6 coins
    
                     9枚硬币 6枚硬币
    
                               玩家 对手
     
    6, 1, 4, 9, 8, 5        6
    1, 4, 9, 8, 5                      5
    1, 4, 9, 8              8
    1, 4, 9                            9
    1, 4                    4
    1                                  1
                         18 coins   15 coins
                         18枚硬币 15枚硬币
    
    

    练习这个问题

    我们的想法是,在明知对手是最优策略的情况下,找到一个使玩家获胜的最优策略。玩家对硬币[i...j]有两个选择,其中i和j分别表示行的前部和后部。

    1. 如果玩家选择了前面的锅i,对手就只能从[i+1, j]中选择。
    
    如果对手选择了前锅i+1,则重复选择[i+2, j]。
    如果对手选择了后锅j,则重复进行[i+1, j-1]。
    2. 如果棋手选择了后锅j,那么对手就只能从[i, j-1]中选择。
    
    如果对手选择了前锅i,则递归为[i+1, j-1]。
    如果对手选择了后边的锅j-1,则重复选择[i, j-2]。
    
    由于对手是以最优化的方式进行游戏,他将试图使玩家的点数最小化,即对手将做出使玩家的硬币最少的选择。因此,我们可以递归地将问题定义为。
    

    伪代码

                             | coin[i] (如果i = j)
     optimalStrategy(i, j) = | max(coin[i], coin[j]) (if i + 1 = j)
                             | max (coin[i] + min(optimalStrategy(coin, i + 2, j),
                                            optimalStrategy(coin, i + 1, j - 1))。
                              coin[j] + min(optimalStrategy(coin, i + 1, j - 1),
                                               optimalStrategy(coin, i, j - 2)))
    
    
    # Recursive function to maximize the number of coins collected by a player,
    # assuming that the opponent also plays optimally
    def findMaxCoins(coin, i, j):
     
        # base case: one pot left, only one choice possible
        if i == j:
            return coin[i]
     
        # if we are left with only two pots, choose one with maximum coins
        if i + 1 == j:
            return max(coin[i], coin[j])
     
        # if a player chooses front pot `i`, the opponent is left to choose from [i+1, j]
        # 1. If the opponent chooses front pot `i+1`, recur for [i+2, j]
        # 2. If the opponent chooses rear pot `j`, recur for [i+1, j-1]
     
        start = coin[i] + min(findMaxCoins(coin, i + 2, j),
                            findMaxCoins(coin, i + 1, j - 1))
     
        # if a player chooses rear pot `j`, the opponent is left to choose from [i, j-1]
        # 1. If the opponent chooses front pot `i`, recur for [i+1, j-1]
        # 2. If the opponent chooses rear pot `j-1`, recur for [i, j-2]
     
        end = coin[j] + min(findMaxCoins(coin, i + 1, j - 1),
                            findMaxCoins(coin, i, j - 2))
     
        # return the maximum of two choices
        return max(start, end)
     
     
    # Pots of gold game using dynamic programming
    if __name__ == '__main__':
     
        # pots of gold (even number) arranged in a line
        coin = [4, 6, 2, 3]
     
        print('The maximum coins collected by player is',
            findMaxCoins(coin, 0, len(coin) - 1))
    

    以下是上述方法的Python实现。

    集装箱码头摆放货柜
    一排摆放的货柜,每个货柜的编号头字母是大写的字母 A->Z
    现在你要编写指令,搬运机器人调整货柜的位置,目标是货柜编号按照字母表的顺序摆放。你考虑到节能减排的需求,尽量做到搬运的次数最少。

    例如:
    'ECADBF',字符串格式表示货柜摆放的位置,目标是调整为 'ABCDEF'
    最佳的搬运方案是:

    D, F不动 !

    1、E和B 交换位置后,'BCADEF'
    2、A和B 交换位置后,'ACBDEF'
    2、C和B 交换位置后,'ABCDEF'
    共搬运 3 次即可完成!

    def swapcaseUP(start):
        s = start
        swap = {}
        cunt = 1
        while s != sorted(s):
            sorder = sorted(start)
      
            for i,c in enumerate(s):
                if i == sorder.index(c):pass
                else:
                    t = sorder.index(c)
                    s[t],s[i] = s[i], s[t]  # 不能用 c !!1
                    swap[cunt] = f"{chr(s[t])} <-> {chr(s[i])}"
                    cunt += 1
    
        return len(swap),swap,''.join([chr(i) for i in s])
    

    输出结果:

    测试用例 >>>
    
    start = 'ABCDEFGHIJK'
    print(f' {start}:',swapcaseUP(start))
    ABCDEFGHIJK: (0, {}, 'ABCDEFGHIJK')
    
    
    start = 'ABCDEFGHIJK'[::-1]
    print(f' {start}:',swapcaseUP(start))
     KJIHGFEDCBA: (5, {1: 'K <-> A', 2: 'J <-> B', 3: 'I <-> C', 4: 'H <-> D', 5: 'G <-> E'}, 'ABCDEFGHIJK')
    
    start = 'FCABDE'
    print(f'{start}:',swapcaseUP(start))
    FCABDE: (5, {1: 'F <-> E', 2: 'C <-> A', 3: 'B <-> A', 4: 'D <-> A', 5: 'E <-> A'}, 'ABCDEF')
    
    start = 'FCADBE'
    print(f' {start}:',swapcaseUP(start))
     FCADBE: (4, {1: 'F <-> E', 2: 'C <-> A', 3: 'B <-> A', 4: 'E <-> A'}, 'ABCDEF')
    
    
    start = 'ECADBF'
    print(f' {start}:',swapcaseUP(start))
     ECADBF: (3, {1: 'E <-> B', 2: 'C <-> A', 3: 'B <-> A'}, 'ABCDEF')
    
    
    start = 'FEDCBA'
    print(f' {start}:',swapcaseUP(start))
     FEDCBA: (3, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C'}, 'ABCDEF')
    
    
    start = 'FEDCBAGH'
    print(f' {start}:',swapcaseUP(start))
     FEDCBAGH: (3, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C'}, 'ABCDEFGH')
    
    start = 'FEDCBAGHIJ'
    print(f'{start}:',swapcaseUP(start))
    FEDCBAGHIJ: (3, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C'}, 'ABCDEFGHIJ')
    
    
    start = 'FEDCBAGHJI'
    print(f' {start}:',swapcaseUP(start))
     FEDCBAGHJI: (4, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C', 4: 'J <-> I'}, 'ABCDEFGHIJ')
    
    start = 'FEDCBAHGJI'
    print(f' {start}:',swapcaseUP(start))
     FEDCBAHGJI: (5, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C', 4: 'H <-> G', 5: 'J <-> I'}, 'ABCDEFGHIJ')
    
    start = 'FAJIHGKEDCB'
    print(f' {start}:',swapcaseUP(start))
     FAJIHGKEDCB: (7, {1: 'F <-> G', 2: 'A <-> G', 3: 'J <-> C', 4: 'I <-> D', 5: 'H <-> E', 6: 'K <-> B', 7: 'G <-> B'}, 'ABCDEFGHIJK')
    
    start = 'DFAECIGBJH'
    print(f' {start}:',swapcaseUP(start))
    DFAECIGBJH: (7, {1: 'D <-> E', 2: 'F <-> I', 3: 'A <-> E', 4: 'C <-> E', 5: 'B <-> I', 6: 'J <-> H', 7: 'I <-> H'}, 'ABCDEFGHIJ')
    
    start = 'CDABEKIHGJF'
    print(f' {start}:',swapcaseUP(start))
     CDABEKIHGJF: (4, {1: 'C <-> A', 2: 'D <-> B', 3: 'K <-> F', 4: 'I <-> G'}, 'ABCDEFGHIJK')
    
    

    团购水果的分配任务

    自发组织团购了一车新鲜水果,预约的货车运载量是5千公斤左右。在疫情期间,我将分给小区的100多家。数量需求不尽不同,还好经验丰富的供货商考虑到水果发放要尽量便捷,提供大小不同的包装袋密封好,到小区后不必打开包装,通过适当的几种包装的组合能够凑出订购的数量。这样做以来,不仅省去每一家称重的时间,也极大地缩短人员聚集的时间。

    你作为程序员更容易想到一个办法,建议水果供货商将水果分为固定的几种若干斤的重量的包装,最终能保证每家来领水果的时候,无论订购了多少斤,一定能找到组合出每一家的水果需求量。同时不会剩下,如何做到看起来近乎不可能的事情,希望你能给出各种包装的数量。

    策略

    首先要准确量出 1 -> n,且 sum(1+ ... +n) <= 5000 less or above

    找到某种规律组合,能够凑出上述任意数量的水果

    每一种包装的数量确定

    def evensplit(n):
        total = 1
        i = 1
        bench = []
        while total <= n:
           bench.append(i)
           total += i
           i += 1
        maxwgt = len(bin(bench[-1])[2:])
        bins = [2**i for i in range(maxwgt+1)][::-1]
        weights = dict(zip(bins,len(bins)*[0]))
    
        for n in bench[::-1]:
            for k,v in weights.items():
                weights[k] += n//k
                n = n % k
        return total,bins,weights
    
    n = 5000
    print('total ',evensplit(n))
    
    total  (5051, [128, 64, 32, 16, 8, 4, 2, 1], {128: 0, 64: 37, 32: 37, 16: 48, 8: 48, 4: 49, 2: 50, 1: 50})
    

    领取水果的分配包装

    输入参数是领取的重量 n

    def pick(n):
        b = bin(n)[2:][::-1]
        ans = [2**int(i) for i,e in enumerate(b) if e == '1']
        return [f"{i} kg 一袋" for i in ans]
    
    n = 10
    print(pick(n))
    
    n = 17
    print(pick(n))
    
    n = 50
    print(pick(n))
    
    n = 100
    print(pick(n))
    
    
    ['2 kg 一袋', '8 kg 一袋']
    ['1 kg 一袋', '16 kg 一袋']
    ['2 kg 一袋', '16 kg 一袋', '32 kg 一袋']
    ['4 kg 一袋', '32 kg 一袋', '64 kg 一袋']
    

    启发商家的包装从小到大可以采用 1,2,4,8,... ..kg的包装

    当然上述例子假设每一家的需求数量都不同时,而且一整车货载重固定5000kg的约束条件下,批发水果的包装大小的组合数量分布。

    进一步看实际遇到有顾客订购相同数量的需求,现在解决起来变得容易。只需稍微修改代码:
    例如:有 5 人 购买 20kg,有 7 人购买 30kg
    我们需要在总量不变的情况下,调整特定包装的数量。

    #1 首先看要增加哪些包装
    s = [(5,20),(7,30)]
    
    def picknum(n):
        b = bin(n)[2:][::-1]
        ans = [2**int(i) for i,e in enumerate(b) if e == '1']
        return ans
    
    # 保持总量的情况下,减少哪些包装
    def adjust(s,n):
        n = n - sum([x*y for x,y in s])
        wgt = evensplit(n)[2]
        t = [(w[0],picknum(w[1])) for w in s]
        for m in t:
            for k,v in wgt.items():
                for i in m[1]:
                    if i == k:
                        wgt[k] += m[0]
    
        return wgt
    
    s,n = [(5,20),(7,30)],5000
    print(adjust(s,n))
    
    {128: 0, 64: 34, 32: 34, 16: 60, 8: 55, 4: 60, 2: 55, 1: 49}
    对比原包装方案:
    {128: 0, 64: 37, 32: 37, 16: 48, 8: 48, 4: 49, 2: 50, 1: 50})
    

    结论是:

    64kg的包装减少 3 袋
    32kg的包装增加 3 袋
    16kg的包装增加 2 袋
    8kg的包装增加 7 袋
    4kg的包装增加 11 袋
    2kg的包装增加 5 袋
    1kg的包装减少 1 袋

    春节抢红包

    我在微信群里亲朋好友的孩子发红包,因为每个孩子都很在意收的红包会不会比其他孩子少,于是我选择没有选择随机红包,而是平均等分红包。

    而我又不想有剩下的钱而退回来。考虑到有孩子会错过抢红包。我计算了以下最少要往红包里转账多少钱,才能保证:

    无论最后有多少孩子来参与抢红包,都能保证每个孩子都能得到相同的金额,同时我又不会剩下的部分钱退回?(请放心保证至少有一个孩子参加抢红包)。

    输入参数 num -> 微信群的人数

    def redPacket(num):
        n = 1
        for i in range(num):
            init = n
            while n % (i+1):
                n += init
        return n
    
    amount_of_kids = 5
    print(f'{amount_of_kids}',redPacket(amount_of_kids))
    5 60
    
    amount_of_kids = 7
    print(f'{amount_of_kids}',redPacket(amount_of_kids))
    7 420
    
    amount_of_kids = 10
    print(f'{amount_of_kids}',redPacket(amount_of_kids))
    10 2520
    

    结论:

    如果邀请5人,无论1-5之间的人数,至少要准备 60 元保证每个抢到相同的数量;

    如果邀请10人,无论来1-10之间的人数,至少要准备 420 元保证抢到相同的数量;

    如果邀请10人,无论来1-10之间的人数,至少要准备 2520 元保证抢到相同的数量;

    相关文章

      网友评论

          本文标题:算法思维训练趣味任务(十四) 适龄高中阶段

          本文链接:https://www.haomeiwen.com/subject/vwugprtx.html