zoukankan      html  css  js  c++  java
  • 循环赛日程安排(构造、分治)

    问题描述

    N支队伍进行比赛,要求:

    • 任意两支队伍交且仅交过一次手
    • 每天每支队伍至多打一场比赛
    • 比赛尽快完成

    问题等价于构造一个N×M矩阵,表示N支队伍比赛M天,第一列固定为1到N,表示每支队伍的编号,第i行表示第i支队伍每天的对手,-1表示没有对手不比赛。
    第i行第j列的数字表示第i队在第j天跟a[i][j]打比赛,那么a[a[i][j]][j]=i必然成立。

    先说结论

    如果N为2的幂,则比赛日程为N×N的矩阵,也就是比赛在N天内完成,这是最完美的方法
    如果N为偶数,则比赛日程也为N×N的矩阵
    如果N为奇数,则比赛日程为N×(N)的矩阵

    当N为2的幂

    当N为2的幂,比赛日程为N阶方阵。
    分治问题为N/2,得到一个N/2的方阵。相当于大方阵均分成四份,求出来左上角那一份来。把那一份进行一些平移变换复制到另外三份。

    from pprint import pprint
    
    
    def go(x):
        if x == 2:
            return [[0, 1], [1, 0]]
        half = x // 2
        a = go(half)
        t = [[0] * x for _ in range(x)]
        for i in range(half):
            for j in range(half):
                t[i][j] = a[i][j]
                t[i + half][j] = a[i][j] + half
                t[i][j + half] = a[i][j] + half
                t[i + half][j + half] = a[i][j]
        return t
    
    N = 2 ** 3
    ans = go(N)
    pprint(ans)
    

    当N不是2的幂

    当N不是2的幂时,分类讨论:

    • 当N为偶数,直接构造N阶方阵
    • 当N为奇数,直接构造N+1阶的方阵,然后删掉最后一行

    所以将一切情况都转化成了偶数阶方阵的构造。下面分类讨论偶数阶方阵的构造:
    记half=N/2

    • 当half为偶数时,直接计算half阶方阵,构成左上角,完成了1/4的任务,然后平移到另外三个位置
    • 当half为奇数时,分两部分完成,左半部分和右半部分。左半部分为前half+1天的安排,右半部分为后half-1天的安排。
      左上角相当于构造half+1阶(half+1必为偶数)的方阵,右上角相当于循环移位操作。
      实际上,只要知道前half队的比赛,就知道了后half队的比赛

    下面看一个11个队参加方阵的例子

    [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1],
     [1, 0, 4, 2, 5, 3, 7, 6, 10, 8, -1, 9],
     [2, 5, 0, 1, 3, 4, 8, -1, 6, 7, 9, 10],
     [3, 4, 5, 0, 2, 1, 9, 10, -1, 6, 8, 7],
     [4, 3, 1, 5, 0, 2, 10, 9, 7, -1, 6, 8],
     [5, 2, 3, 4, 1, 0, -1, 8, 9, 10, 7, 6],
     [6, 7, 8, 9, 10, -1, 0, 1, 2, 3, 4, 5],
     [7, 6, 10, 8, -1, 9, 1, 0, 4, 2, 5, 3],
     [8, -1, 6, 7, 9, 10, 2, 5, 0, 1, 3, 4],
     [9, 10, -1, 6, 8, 7, 3, 4, 5, 0, 2, 1],
     [10, 9, 7, -1, 6, 8, 4, 3, 1, 5, 0, 2]]
    
    from pprint import pprint
    
    
    def check(a):
        # 检查答案是否正确
        print('{}行{}列'.format(len(a), len(a[0])))
        for i in range(0, len(a)):
            for j in range(1, len(a[0])):
                if a[i][j] != -1:
                    if i != a[a[i][j]][j]:
                        print("第{}天第{}人错误".format(j, a[i][j]))
                        return
        print("完全正确")
    
    
    def f(N):  # N一定为偶数
        if N == 2:
            return [[0, 1], [1, 0]]
        half = N // 2
        a = go(half)
        if half % 2 == 0:
            t = [[-1] * N for _ in range(N)]
            for i in range(half):
                for j in range(half):
                    t[i][j] = a[i][j]
                    t[i + half][j] = a[i][j] + half
                    t[i][j + half] = a[i][j] + half
                    t[i + half][j + half] = a[i][j]
            return t
        else:
            t = [[-1] * N for _ in range(N)]
            for i in range(half):
                for j in range(half + 1):  # 对于前half+1项
                    t[i][j] = a[i][j]
                    t[i + half][j] = (a[i][j] + half) % N
                t[i][half - i] = half + i
                t[half + i][half - i] = i
                for j in range(half + 1, N):  # 对于后half-1项
                    t[i][j] = (i + j) % half + half
                    t[t[i][j]][j] = i
            return t
    
    
    def go(N):
        if N % 2 == 0:
            a = f(N)
            return a
        else:
            a = f(N + 1)  # 需要去除最下面一行和最左面一列
            t = [[a[i][j] if a[i][j] != N else -1 for j in range(N + 1)] for i in range(N)]
            return t
    
    
    N = 5
    ans = go(N)
    check(ans)
    pprint(ans)
    
    
    

    参考资料

    李健勇, 李晔, 刘艳红,等. 任意数量选手的循环赛赛程分治算法[J]. 郑州轻工业学院学报自然科学版, 2007, 22(4):122-125.

  • 相关阅读:
    CC_UNUSED_PARAM 宏含义的解释
    JAVA 字符串编码总结
    cocos-html5 Json 灵活 遍历方式 不同方式的缺陷,优点总结
    selenium--获取HTML源码断言和URL地址
    django -- 路由
    selenium--表格和复选框的定位
    selenium--加载浏览器配置
    django -- 视图
    django -- 母版继承
    selenium--浏览器窗口截图
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/6783379.html
Copyright © 2011-2022 走看看