题目说明:
设有n=2k(k为上标)个选手要进行循环赛,要求设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次。
按此要求,可将比赛日程表设计成一个 n 行n-1列的二维表,其中,第 i 行第 j 列表示和第 i 个选手在第 j 天比赛的选手。
此题采用分治策略,通过在k=1和k=2时找到打印规律:
k=1: 1 2 k=2: 1 2 3 4
2 1 2 1 4 3
3 4 1 2
4 3 2 1
第一列为所有选手,每一行的除了第一列外,剩下的每一列都是该选手在接下来的n-1天要面对的比赛对手。通过观察,可以发现,左上角和右下角的数字相同,左下角和右上角的数字相同(对于k=2,以k=1的规模为整体观察)。
程序设计思路(c++实现):因为C++不能按指定行打印,所以先创建一个足够大的二维数组将结果先存入其中,如a[N][N]。在n=2*k个选手比赛时,先确定左上角的数据和左下角的数据,在利用对称复制,将左下角的数据复制到右上角,将左上角的数据复制到右下角。详细说明请查看代码注释。
#include <iostream>
using namespace std;
const int N = 50;
int a[N][N]; //定义足够大的数组
void dim(int i, int j, int n) {
int k1, k2;
int mid = n / 2;
if (n == 2) { //当递归到只有两个选手时,i=1,j=2,n=2
a[i][n] = j; //右上角为2
a[j][n] = i; //右下角为1
a[i][n - 1] = i; //左上角为1
a[j][n - 1] = j; //左下角为2
} else {
dim(i, i + mid - 1, mid); //递归一半mid,开始为i,结束为i到mid的个数,即i+mid-1,规模为mid
dim(i + mid, j, mid); //递归另一半mid,开始为i+mid,j,规模为mid
//此for循环中,需要复制规模为mid行mid列,一趟循环先复制第n列,再一趟循环复制第n-1列,直到第mid+1列
//对于复制的下标的确定,可以想象n=2*2个选手时的下标跟着下面的循环走一遍
for (k1 = n; k1 > mid; k1--) {
//以下for循环的边界的确定k2 <= i + mid - 1和k2 <= i + n - 1,
//可以用k2的初始值+mid的大小确定。循环次数为mid
for (k2 = i; k2 <= i + mid - 1; k2++) { //此for循环将左下角的数据复制到右上角
a[k2][k1] = a[k2 + mid][k1 - mid];
}
for (k2 = i + mid; k2 <= i + n - 1; k2++) { //此for循环将左上角的数据复制到右下角
a[k2][k1] = a[k2 - mid][k1 - mid];
}
}
}
}
int main() {
int n = 1, i, j, k;
scanf("%d", &k); //输入k,但是选手是n=2k(k为上标),即2的k次方个选手
for (i = 1; i <= k; i++) //所以n是2的k次方,打印的日程表是n×n的表格。
n = n * 2;
dim(1, n, n); //第一个参数是第一行,第二个参数是第n行,第三个参数为要处理n行。
for (i = 1; i <= n; i++) { //递归完成,双重for循环打印二维数组
cout << endl;
for (j = 1; j <= n; j++) {
cout << a[i][j] << " ";
}
}
return 0;
}