例5 比赛安排(1996年全国青少年信息学(计算机)奥林匹克分区联赛高中组复赛试题) 设有2^n(n<=6)个球队进行单循环比赛,计划在2^n-1天内完成,每个队每天进行一场比赛,设计一个比赛安排,使在2^n-1天内每个队都与不同的对手比赛。例如n=2时的比赛安排为: 队 1 2 3 4 时间 比赛 1 – 2 3 – 4 第一天 1 – 3 2 – 4 第二天 1 – 4 2 – 3 第三天 分析:已知参加比赛的球队数x(x=2^n),要设计一个比赛安排表。分析题目要求(单循环比赛的比赛规则),我们可以归纳出比赛安排表必须满足的条件: ①球赛需要在x-1天内完成。 ②每个球队每天有且只能有一场比赛。 ③任意两个球队必须进行过一场比赛,并且只能进行一场比赛。 当只有两个球队进行比赛时,比赛安排表的设计非常简单,只要让两个球队直接进行一场比赛就可以了。当不止两个球队时,我们可以将所有的球队分成两半。x个球队的比赛安排表就可以通过x/2个球队的比赛安排表来产生。这样递归地使用这种一分为二的方法对球队进行分割,直到只剩下两个球队。 日期\球队 1 2 3 4 5 6 7 8 2 2 1 4 3 6 5 8 7 3 3 4 1 2 7 8 5 6 4 4 3 2 1 8 7 6 5 5 5 6 7 8 1 2 3 4 6 6 5 8 7 2 1 4 3 7 7 8 5 6 3 4 1 2 8 8 7 6 5 4 3 2 1 如图,是一张8个球队的比赛安排表。其中第i行与第j列相交的数字a(i,j)表示第j个球队在第i-1天所遇到的对手。(表的第一行为8个球队的编号)如果用a(i1..i2,j1..j2)表示表中从第i1行到第i2行与从第j1列到第j2列相交的部分,那么,这张8×8的表a(1..8,1..8)可以分成左上角a(1..4,1..4)、右上角a(1..4,5..8)、左下角a(5..8,1..4)和右下角a(5..8,5..8)四个部分。仔细分析这四个部分,我们会发现:右下角a(5..8,5..8)和左上角a(1..4,1..4)完全相同;左下角a(5..8,1..4)和右上角a(1..4,5..8)完全相同。再继续分析每一个部分,都具有同样的规律。比如左上角a(1..4,1..4),我们又可以把它分成a(1..2,1..2)、a(1..2,3..4)、a(3..4,1..2)和a(3..4,3..4)四个部分,并且a(3..4,3..4)和a(1..2,1..2)完全相同, a(3..4,1..2)和a(1..2,3..4)完全相同。 根据上面分析出来的规律,使用分治算法的思想,我们要设计一张8×8的比赛安排表a(1..8,1..8),只需要设计出表的上半部分,即两张4×4的安排表a(1..4,1..4)和a(1..4,5..8)即可。之后,通过已经设计出来的表的左上角a(1..4,1..4)复制出表的右下角a(5..8,5..8),通过右上角a(1..4,5..8)复制出表的左下角a(5..8,1..4)。即一张8×8的比赛安排表a(1..8,1..8),可以通过两张4×4的比赛安排表a(1..4,1..4)和a(1..4,5..8)产生。同样,4×4的比赛安排表a(1..4,1..4),可以通过两张2×2的比赛安排表a(1..2,1..2)和a(1..2,3..4)产生;a(1..4,5..8)可以通过a(1..2,5..6)和a(1..2,7..8)产生。 分解到最后,a(1,1)、a(1,2)、……、a(1,8)都是单独的一个数字,分别为8个球队的编号1、2、……、8,即这张8×8的比赛安排表的第一行。 如何将a(1,1)、a(1,2)、……、a(1,8)这8个数字合并成一张完整的比赛安排表(原问题的解)呢?我们可以将整个合并过程分成三个阶段: 第一阶段,将这8个已知的数字(可以看成8张1×1的比赛安排表)合并成4张2×2的比赛安排表。这一阶段需要进行4次合并操作:①将a(1,1)和a(1,2)合并成a(1..2,1..2);②将a(1,3)和a(1,4)合并成a(1..2,3..4);③将a(1,5)和a(1,6)合并成a(1..2,5..6);④将a(1,7)和a(1,8)合并成a(1..2,7..8)。这一阶段产生表的前两行。 第二阶段,将表的第一二行(可以看成4张2×2的比赛安排表)合并成两张4×4的比赛安排表。这一阶段需要进行两次合并操作:①将a(1..2,1..2)和a(1..2,3..4)合并成a(1..4,1..4);②将a(1..2,5..6)和a(1..2,7..8)合并成a(1..4,5..8)。这一阶段产生表的前四行。 第三阶段,将表的前四行(可以看成2张4×4的比赛安排表)合并成一张8×8的比赛安排表。这一阶段只需要进行一次合并操作:将a(1..4,1..4)和a(1..4,5..8)合并成a(1..8,1..8)。这一阶段将产生一张完整的比赛安排表。 #include<stdio.h> #define MAX 64 void Arrange(int a[][MAX], int n, int left, int right); int main() { int a[MAX][MAX] = {0}; int i, j, n = 8; for (i=0; i<n; ++i)//先确定第1行:球队的编号 a[0][i] = i + 1; Arrange(a, n, 0, n-1);//递归产生各个方阵 for (i=0; i<n; i++) { for (j=0; j<n; j++) printf("%3d", a[i][j]); printf("\n"); } getchar(); return 0; } /* 函数功能:比赛安排,为n(=2^k)个球队安排比赛,使在n-1天内每个队都与不同的对手比赛 输入变量: int a[][MAX], 数组a中存储各球队的比赛情况 int n, 球队的数量,也即方阵的大小 int left, 数组a的左边界 int right, 数组a的右边界 输出变量:int a[][MAX], 数组a中存储各球队的比赛情况 返回值: 无 */ void Arrange(int a[][MAX], int n, int left, int right)//递归产生各个方阵 { if (n < 2)//球队数量少于2,不做任何操作 return; int i, j; int mid = (left+right)/2;//把方阵分成两半 n /= 2; Arrange(a, n, left, mid);//递归处理左上角方阵 Arrange(a, n, mid+1, right);//递归处理右上角方阵 for (i=0; i<n; i++) { for (j=left; j<=mid; j++)//已经得到方阵的左上角,则由左上角推出右下角 { a[i+n][j+n] = a[i][j]; } for (j=mid+1; j<=right; j++)//已经得到方阵的右上角,则由右上角推出左下角 { a[i+n][j-n] = a[i][j]; } } } 也可以写成非递归的方式: /* 函数功能:比赛安排,为n(=2^k)个球队安排比赛,使在n-1天内每个队都与不同的对手比赛 输入变量: int a[][MAX], 数组a中存储各球队的比赛情况 int max, 球队的数量,也即方阵的大小 输出变量:int a[][MAX], 数组a中存储各球队的比赛情况 返回值: 无 */ void Arrange(int a[][MAX], int max) { int i, j, k, n, left, right, mid; for (i=0; i<max; ++i)//先确定第1行:球队的编号 a[0][i] = i + 1; for (n=2; n<=max; n*=2)//依次安排2,4,8。。。个球队 { for (left=0; left<max-1; left+=n)//当前被安排的球队的左边界 { right = left + n - 1;//当前被安排的球队的右边界 mid = (left + right) / 2; k = n / 2;//将球队分成左右两半处理,每半有k个球队 for (i=0; i<k; i++) { for (j=left; j<=mid; j++)//由左上角推出右下角 { a[i+k][j+k] = a[i][j]; } for (j=mid+1; j<=right; j++)//由右上角推出左下角 { a[i+k][j-k] = a[i][j]; } } } } } 练习 1. 计算x的N次方(N>=0)。 2.元三次方程求解(noip2001tg) 问题描述 有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数 (a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间), 且根与根之差的绝对值>=1。 要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后5位。 提示:记方程f(x)=0,若存在2个数x1和x2,且x1<x2,f(x1)*(x2)<0,则在(x1,x2)之间一定有一个根。 样例 输入:1 -5 -4 20 输出:-2.00 2.00 5.00 3.例4的分而治之算法扩充到n> 1个硬币的情形。需要进行多少次重量的比较?

评论