【问题描述】
设有n = 2^k 个选手要进行网球循环赛,设计一个满足以下要求的比赛日程表:
1、每个选手必须与其它n-1个选手各赛一次
2、每个选手一天只能赛一次
3、循环赛在n-1天之内结束
【问题求解】
按照问题要求可将比赛日程表设计成一个n行n-1列的二维表,其中第i行,第j列表示第i个选手在第j天比赛的选手
假设n位选手被顺序编号为1, 2, ... , n ( 2^k) ,当 k = 1,2,3是比赛日程表如下图所示,其中第一列递增,取值为1~n,表示1~n各个选手,这样比赛日程表变成了一个n行n列的二维表
从图中可以看出规律,k = 1只有两个选手比赛安排十分简单,而k = 2时i可以基于k = 1的结果进行安排,k = 3时可以基于k = 2的结果进行安排。
仔细观察k = 3的比赛日程表,右下角的值与左上角的值相等,左下角的值与右上角的值相等(4行4列),并且k = 3的左上角(4行4列)的值等于k = 2(即4个选手)的比赛日程表,左下角的值等于左上角的值加上数字4(2^(k-1)),因此,可以采用分治策略将所有的选手分为两半,2^k个选手的比赛日程表就可以通过为2^(k-1)个选手设计的比赛日程来决定。将n = 2 ^ k 问题划分为4个部分,
(1)、左上角:左上角为2^(k-1)个选手在前半程的比赛日程(k=1时直接给出,否则上一轮求出的 就是2^(k-1)个选手的比赛日程)
(2)、左下角:左下角为另2^(k-1)个选手在前半程的比赛日程,由左上角加2^(k-1)得到,例如2个选 手比赛, 左下角由左上角直接加2(2^(k-1))得到,2^3个选手比赛,左下角由左上角直接加 4(2*-1)得到。
(3)、右上角:将左下角直接复制到右上角得到另2^(k-1)个选手在后半程的比赛日程。
(4)、右下角:将左上角直接复制到右下角得到2^(k-1)个选手在后半程的比赛日程。
实现代码如下所示:
#include "iostream"
using namespace std;
const int MAX = 101;
int k;
//求解结果表示
int ans[MAX][MAX]; //存放比赛日程表(行,列下标为0的元素不用)
/**
* 输入k,共有n = 2^k个选手,ans为返回结果表示安排好的日程
* @param k
*/
void plan(int k) {
int n = 2;
//n从2^1 = 2开始
ans[1][1] = 1;
ans[1][2] = 2;
ans[2][1] = 2;
ans[2][2] = 1;
for (int i = 0; i < k; ++i) {
int temp = n; //temp = 2 依次处理2^2,2^3,...2^k个选手
n *= 2; //n = 4
//填充左下角元素,左下角元素等于左上角元素+temp
for (int p = temp + 1; p <= n; p++) {
for (int q = 1; q <= temp; q++) {
ans[p][q] = ans[p - temp][q] + temp;
}
}
//填充右上角元素,右上角元素与左下角元素相同
for (int p = 1; p <= temp; p++) {
for (int q = temp + 1; q <= n; q++) {
ans[p][q] = ans[p + temp][q - temp];
}
}
//填充右下角元素,右下角元素与左上角元素相同
for (int p = temp + 1; p <= n; p++) {
for (int q = temp + 1; q <= n; q++) {
ans[p][q] = ans[p - temp][q - temp];
}
}
}
}
int main() {
k = 3;
int n = 1 << k;
plan(k);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
cout << ans[i][j] << " ";
cout << endl;
}
}
运行结果如下所示:
修改k的值为4可以发现日程表如下所示: