分治法解决循环赛日程表 C++

分治法解决循环赛日程表

一、问题描述:

循环赛日程表是一种安排参与者之间多次比赛的表格,每个参与者都要与其他参与者进行比赛。具体要求如下:

(1)每个选手必须与其他n-1个选手各赛一次;

(2)每个选手一天只能参赛一次;

(3)循环赛在n-1天内结束

按此要求将比赛日程表设计成有 n 行 n-1 列的表格。在表格的第 i 行第 j 列填入第 i 名选手于第 j 天遇到的对手

二、问题分析:

2.1暴力搜索

首先尝试直接放置的策略,记录每个人第 j 天和那个人比赛,同时标记每个人参加过那一天的比赛。如果当天没有安排且有个人在这天没有比赛就直接安排这个人。经过实验发现这样的策略有一定的缺陷,即在我们安排一个人之后,下一个人已经没有位置来安排比赛,案例如下

2 3 4 5 6 7 8
1 8 3 4 5 6 7
4 1 2 7 8 5 6
3 0 1 2 0 0 0
0 0 0 1 2 3 0
0 0 0 0 1 2 3
0 0 0 3 0 1 2
0 2 0 0 3 0 1

这里尝试直接安排日程并已经排好了了前三个人的日程表,在安排第四个人的日程时,若我们将5安排在第二天,那么第6个人将没有位置可以安排,这种问题可以通过 dfs 回溯解决。

2.2递归分治

对于人数较多难以采用dfs计算的情况可以尝试用分治策略划分为规模较小的子问题,按分治策略,我们可以将所有的选手分为两半,则n个选手的比赛日程表可以通过n/2个选手的比赛日程表来决定。递归地用这种一分为二的策略对选手进行划分,直到只剩下两个选手时,比赛日程表的制定就变得很简单。这时只要让这两个选手进行比赛就可以了,由此可以得到多个两人一组,大小为 2 1 的日程表,下面给出1,2和3,4的日程表:

选手\日期day1
12
21
选手\日期day1
34
43

要得到选手1、2、3、4的日程表,需要合并上面两个表格,可以发现第一天的日程安排并不冲突,可以直接拼接完成,而后续两天的日程,可以交换上下顺序直接拼接在day1后面(包括左侧表头)

选手\日期day1day2day3
1234
2143
3412
4321

由此又得到了四人一组的日程表(三人一组的无法制成一个合法的日程表,因为没有单个人的比赛日程,无法拼接成三个人,但是可以添加一个空缺位来完成,只有2的整数次幂的选手个数可以构造出满数据的日程表)

选手\日期day1day2day3
123
213
312

经过上面分析,可以得到完整的循环赛日程表构造方式接下来是实现过程。

三、代码实现:

由上文可知,想要构造出合法的循环赛日程表首先要依据选手数量得出合理的分治策略,如果选手数量是2的整数次幂,我们可以直接对其二分计算,对于其他情况则需要向上补至2的整数次幂再求解。

    int n;
    cin >> n;
    int len = 2;
    while(len < n)  len *= 2;   //得到分治的区间大小

分治的最小规模是只有两个人,这里可以直接对子表格进行赋值

    if(r - l == 1){
        calendar[l][1] = player[l];
        calendar[l][2] = player[r];
        calendar[r][1] = player[r];
        calendar[r][2] = player[l];
        return;
    }

在完成分治之后,还需要将两个子问题合并,这里可以直接将完成的子表格交换上下顺序后复制得到

void merge(int l, int mid, int r){
    int len = (r - l + 1) >> 1;
    for(int i = len + 1; i <= 2*len; i ++){//只需要更新后续(r-l+1)/2天的数据
        for(int j = l; j <= mid; j ++)
            calendar[j+len][i] = calendar[j][i-len];
        for(int j = mid + 1; j <= r; j ++)
            calendar[j-len][i] = calendar[j][i-len];
    }
}
完整代码:
#include<iostream>
using namespace std;
const int P_SIZE = 1e3 + 10;
int calendar[P_SIZE][P_SIZE];
int player[P_SIZE];
​
void merge(int l, int mid, int r){
    int len = (r - l + 1) >> 1;
    for(int i = len + 1; i <= 2*len; i ++){
        for(int j = l; j <= mid; j ++)
            calendar[j+len][i] = calendar[j][i-len];
        for(int j = mid + 1; j <= r; j ++)
            calendar[j-len][i] = calendar[j][i-len];
    }
}
​
void dfs(int l, int r){
    if(r - l == 1){
        calendar[l][1] = player[l];
        calendar[l][2] = player[r];
        calendar[r][1] = player[r];
        calendar[r][2] = player[l];
        return;
    }
    int mid = l+r >> 1;
    dfs(l, mid);
    dfs(mid+1, r);
    merge(l, mid, r);
}
​
int main(){
    int n;
    cin >> n;
    
    int len = 2;
    while(len < n)  len *= 2;
    for(int i = 1; i <= min(P_SIZE-1, len); i ++)//初始化参赛选手
        if(i <= n)  player[i] = i;
        else        player[i] = 0;
    
    dfs(1, len);
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= len; j ++)
            cout << calendar[i][j] <<' ';
        cout << endl;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值