end;
begin
randomize;
fori:
=1tomaxdobegina[i]:
=random(1000);write(a[i]:
6);end;
writeln;
qsort(1,max);
fori:
=1tomaxdowrite(a[i]:
6);
readln;
end.
例2、循环比赛日程表(match.?
?
?
)
设有n个选手的循环比赛,其中n=2m,要求每名选手要与其他n-1名选手都赛一次。
每名选手每天比赛一次,循环赛共进行n-1天。
要求每天没有选手轮空.以下是八名选手时的循环比赛表,表中第一行为八位选手的编号,下面七行依次是每位选手每天的对手。
123456782143658734127856432187655678123465872143
78563412
87654321
[问题分析]
从八位选手的循环比赛表中可以看出,这是一个具有对称性的方阵,可以把方阵一分为四来看,那么左上角的4*4的方阵就是前四位选手的循环比赛表,而右上角的4*4的方阵就是后四位选手的循环比赛表,它们在本质上是一样的,都是4个选手的循环比赛表,所不同的只是选手编号不同而已,将左上角中方阵的所有元素加上4就能得到右上角的方阵.下方的两个方阵表示前四位选手和后四位选手进行交叉循环比赛的情况,同样具有对称性,将右上角方阵复制到左下角即得到1,2,3,4四位选手和5,6,7,8四位选手的循环比赛表,根据对称性,右下角的方阵应与左上角的方阵相同.这样,八名选手的循环比赛表可以由四名选手的循环比赛表根据对称性生成出来.同样地,四名选手的循环比赛表可以由二名选手的循环比赛表根据对称性生成出来,而两名选手的循环比赛表可以说是已知的,这种程序设计方法叫做分治法,其基本思想是把一个规模为n的问题分成若干个规模较小的问题,使得从这些较小问题的解易于构造出整个问题的解.
程序中用数组matchlist记录n名选手的循环比赛表,整个循环比赛表从最初的1*1的方阵按上述规则生成出2*2的方阵,再生成出4*4的方阵,……,直到生成出整个循环比赛表为止.变量half表示当前方阵的大小,也是要生成的下一个方阵的大小的一半.
[参考程序]
programmatch;
constmaxn=32;maxm=5;vari,j,k,m,n,half:
integer;matchlist:
array[1..maxn,1..maxn]ofinteger;beginwrite('Inputm:
');readln(m);
n:
=1;fori:
=1tomdon:
=n*2;
k:
=1;matchlist[1,1]:
=1;half:
=1;
whilek<=mdo
begin
fori:
=1tohalfdo{构造右上角方阵}
forj:
=1tohalfdomatchlist[i,j+half]:
=matchlist[i,j]+half;
fori:
=1tohalfdo{对称交换构造下半部分方阵}
forj:
=1tohalfdo
begin
matchlist[i+half,j]:
=matchlist[i,j+half];
matchlist[i+half,j+half]:
=matchlist[i,j]
end;
half:
=half*2;k:
=k+1
end;
fori:
=1tondo
begin
forj:
=1tondowrite(matchlist[i,j]:
4);
writeln
end;
end.
[样例]
Inputm:
4
Output:
1234567891011121314151621436587109121114131615
34127856111291015161314
43218765121110916151413
56781234131415169101112
65872143141316151091211
78563412151613141112910
87654321161514131211109
91011121314151612345678
10912111413161521436587
11129101516131434127856
12111091615141343218765
13141516910111256781234
14131615109121165872143
15161314111291078563412
16151413121110987654321
例3、小车问题(car.?
?
?
)
[问题描述]
甲、乙两人同时从A地出发要尽快同时赶到B地。
出发时A地有一辆小车,可是这辆小车除了驾驶员外只能带一人。
已知甲、乙两人的步行速度一样,且小于车的速度。
问:
怎样利用小车才能使两人尽快同时到达。
[问题输入]
仅一行,三个整数,分别表示AB两地的距离s米(≤2000),人的步行速度a米/秒,车的速度b米/秒,2000>b>a。
[问题输出]
两人同时到达B地需要的最短时间,单位秒,保留2位小数。
[输入输出样例]
car.in
120525
car.out
9.60
[问题分析]
最佳方案为:
甲先乘车到达K处后下车步行,小车再回头接已走到C处的乙,在D处相遇后,乙再乘车赶往B,最后甲、乙一起到达B地。
如下图所示,这时所用的时间最短。
这样问题就转换成了求K处的位置,我们用二分法,不断尝试,直到满足同时到达的时间精度。
算法框架如下:
1、输入s,a,b;
2、c0:
=0;c1:
=s;c:
=(c0+c1)/2;
3、求t1,t2;
4、如果t1=(c0+c)/2
否则c:
=(c+c1)/2;
反复执行3和4,直到abs(t1-t2)满足精度要求(即小于误差标准)。
[参考程序]
programcar(input,output);
constzero=1e-4;
vars,a,b,c,c0,c1,t1,t2,t3,t4:
real;
begin
assign(input,’car.in’);
assign(output,’car.out’);
reset(input);rewrite(output);
readln(s,a,b);
c0:
=0;c1:
=s;
repeat
c:
=(c0+c1)/2;
t3:
=c/b;
t1:
=t3+(s-c)/a;
t4:
=(c-t3*a)/(a+b);
t2:
=t3+t4+(s-(t3+t4)*a)/b;
ift1=celsec0:
=c;
untilabs(t1-t2)writeln(t1);
close(input);close(output)
end.
例4、取余运算(mod.?
?
?
)
[问题描述]
输入b,p,k的值,求bpmodk的值。
其中b,p,k*k为长整形数。
[输入输出样例]
mod.in
2109
mod.out
2^10mod9=7
[问题分析]
本题主要的难点在于数据规模很大(b,p都是长整型数),对于bp显然不能死算,那样的话时间复杂度和编程复杂度都很大。
下面先介绍一个原理:
A*BmodK=(AmodK)*(BmodK)modK。
显然有了这个原理,就可以把较大的幂分解成较小的,因而免去高精度计算等复杂过程。
那么怎样分解最有效呢?
显然对于任何一个自然数P,有P=2*Pdiv2+Pmod2,如19=2*19div2+19mod2=2*9+1,利用上述原理就可以把B的19次方除以K的余数转换为求B的9次方除以K的余数,即B19=B2*9+1=B*B9*B9,再进一步分解下去就不难求得整个问题的解。
这是一个典型的分治问题,具体实现的时候是用递推的方法来处理的,如P=19,有19=2*9+1,9=2*4+1,4=2*2+0,2=2*1+0,1=2*0+1,反过来,我们可以从0出发,通过乘以2再加上一个0或1而推出1,2,4,9,19,这样就逐步得到了原来的指数,进而递推出以B为底,依次以这些数为指数的自然数除以K的余数。
不难看出这里每一次乘以2后要加的数就是19对应的二进制数的各位数字,即1,0,0,1,1,而19=(10011)2,求解的过程也就是将二进制数还原为十进制数的过程。
具体实现请看下面的程序,程序中用数组binary存放P对应的二进制数,总位数为len,binary[1]存放最底位。
变量rest记录每一步求得的余数。
[参考程序]
programmod(input,output);
varb,p,k,i,len,rest,temp:
longint;
binary:
array[1..32]oflongint;
begin
assign(input,’mod.in’);
assign(output,’mod.out’);
reset(input);rewrite(output);
readln(b,p,k);{输入三个数}
len:
=0;
temp:
=p;
whiletemp<>0do{存放p的二进制转换}
begin
len:
=len+1;
binary[len]:
=tempmod2;
temp:
=tempdiv2
end;
rest:
=1;
fori:
=lendownto1do{用二分法实现b^pmodk}
begin
temp:
=rest*restmodk;
ifbinary[i]=1thenrest:
=bmodk*tempmodk{如果是奇数,就多乘b}
elserest:
=temp{否则就是rest*rest}
end;
writeln(b,'^',p,'mod',k,'=',rest);{输出b^pmodk}
close(input);close(output)
end.
[类似问题]NOIP2003初中组麦森数
例5、地毯填补问题(blank.?
?
?
)——棋盘覆盖问题
[问题描述]
相传在一个古老的阿拉伯国家里,有一座宫殿。
宫殿里有个四四方方的格子迷宫,国王选择驸马的方法非常特殊,也非常简单:
公主就站在其中一个方格子上,只要谁能用地毯将除公主站立的地方外的所有地方盖上,美丽漂亮聪慧的公主就是他的人了。
公主这一个方格不能用地毯盖住,毯子的形状有所规定,只能有四种选择(如下图):
并且每一方格只能用一层地毯,迷宫的大小为2的k次方见方的方形。
当然,也不能让公主无限制的在那儿等,对吧?
由于你使用的是计算机,所以实现时间为1秒。
[输入]
输入文件共2行。
第一行:
k,即给定被填补迷宫的大小为2k(0第二行:
xy,即给出公主所在方格的坐标(x为行坐标,y为列坐标),x和y之间有一个空格隔开。
[输出]
将迷宫填补完整的方案:
每一补(行)为xyc(x,y为毯子拐角的行坐标和列坐标,c为使用毯子的形状,具体见上面的图1,毯子形状分别用1、2、3、4表示,x、y、c之间用一个空格隔开)。
[输入输出样例]
blank.in
3
33
blank.out
551
224
114
143
412
441
273
154
183
363
481
722
514
632
812
841
771
661
583
852
881
[问题算法分析]
拿到这个问题后,便有一种递归重复的感觉。
首先对最简单的情况(即k=1)进行分析:
公主只会在4个方格中的一个:
左上角:
则使用3号毯子补,毯子拐角坐标位于(2,2);{下面就简称为毯子坐标}
左下角:
则使用2号毯子补,毯子拐角坐标位于(1,2);
右上角:
则使用1号毯子补,毯子拐角坐标位于(2,1)
右下角:
则使用4号毯子补,毯子拐角坐标位于(1,1);
其实这样不能说明什么问题,但是继续讨论就会有收获,即讨论k=2的情况(如下图):
#
#
#
●
#
○
#
#
#
○
○
#
#
#
#
#
我们假设公主所在的位置用实心圆表示,即上图中的(1,4),那么我们就可以把1号毯子放在(2,3)处,这样就将(1,3)至(2,4)的k=1见方全部覆盖(#表示地毯)。
接下来就是3个k=1的见方继续填满,这样问题就归结为k=1的情况了,但是有一点不同的是:
没有“公主”了,每一个k=1的小见方都会留下一个空白(即上图中的空心圆),那么空白就有:
1*3=3个,组合后便又是一个地毯形状。
好了,现在有感觉了吧,我们用分治法来解决它!
对于任意k>1的宫殿,均可以将其化分为4个k/2大小的宫殿,先看一下公主站的位置是属于哪一块,因为根据公主所在的位置,我们可以确定中间位置所放的毯子类型,再递归处理公主所站的那一块,直到出现边界条件k=1的情况,然后在公主边上铺上一块合适的地毯,递归结束。
由于要递归到每一格,复杂度就是面积,就是O(22*k*k)。
[参考程序]
programblank(input,output);
varg:
array[1..1024,1..1024]ofboolean;
n:
integer;
procedureinit;
vark,be,en:
integer;
begin
assign(input,'blank.in');
assign(output,'blank.out');
reset(input);
rewrite(output);
readln(k);
n:
=2**k;
fillchar(g,sizeof(g),true);
readln(be,en);
g[be,en]:
=false
end;
functionfind(x1,y1,x2,y2:
integer):
integer;{寻找当前子问题中已被覆盖的点}
vari,t:
integer;
begin
fori:
=x1to(x2+x1)div2do
fort:
=y1to(y2+y1)div2do
ifnotg[i,t]thenexit
(1)
elseifnotg[x2-i+1,t]thenexit(3)
elseifnotg[i,y2-t+1]thenexit
(2)
elseifnotg[x2-i+1,y2-t+1]thenexit(4)
end;
procedurework(x1,y1,x2,y2:
integer);{分治递归求解,从(x1,y1)到(x2,y2)}
vartyp,i,t:
integer;
begin
ifx2-x1=1then{当前已缩小为最简问题}
begin
typ:
=0;{表示该使用的地毯类型}
fori:
=x1tox2do
fort:
=y1toy2do
begin
inc(typ);
ifnotg[i,t]then{此坐标点被覆盖}
casetypof{输出}
1:
writeln(x2,'',y2,'',typ);
2:
writeln(x2,'',y1,'',typ);
3:
writeln(x1,'',y2,'',typ);
4:
writeln(x1,'',y1,'',typ)
end
end
end
else{递归分治}
begin
typ:
=find(x1,y1,x2,y2);{通过寻找已被覆盖的点求出此时使用的地毯类型}
casetypof
1:
begin
g[(x1+x2)div2+1,(y1+y2)div2+1]:
=false;
g[(x1+x2)div2,(y1+y2)div2+1]:
=false;
g[(x1+x2)div2+1,(y1+y2)div2]:
=false;
writeln((x1+x2)div2+1,'',(y1+y2)div2+1,'',1)
end;
2:
begin
g[(x1+x2)div2+1,(y1+y2)div2]:
=false;
g[(x1+x2)div2,(y1+y2)div2]:
=false;
g[(x1+x2)div2+1,(y1+y2)div2+1]:
=false;
writeln((x1+x2)div2+1,'',(y1+y2)div2,'',2)
end;
3:
begin
g[(x1+x2)div2,(y1+y2)div2+1]:
=false;
g[(x1+x2)div2,(y1+y2)div2]:
=false;
g[(x1+x2)div2+1,(y1+y2)div2+1]:
=false;
writeln((x1+x2)div2,'',(y1+y2)div2+1,'',3)
end;
4:
begin
g[(x1+x2)div2,(y1+y2)div2]:
=false;
g[(x1+x2)div2,(y1+y2)div2+1]:
=false;
g[(x1+x2)div2+1,(x1+x2)div2]:
=false;
writeln((x1+x2)div2,'',(y1+y2)div2,'',4)
end
end;
work(x1,y1,(x1+x2)div2,(y1+y2)div2);{对左上角分治}
work(x1,(y1+y2)div2+1,(x1+x2)div2,y2);{对右上角分治}
work((x1+x2)div2+1,y1,x2,(y1+y2)div2);{对左下角分治}
work((x1+x2)div2+1,(y1+y2)div2+1,x2,y2){对右下角分治}
end
end;
Begin{main}
init;
work(1,1,n,n);{对(1,1)到(n,n)范围内求解}
close(input);
close(output)
end.