分治法Word文件下载.docx
《分治法Word文件下载.docx》由会员分享,可在线阅读,更多相关《分治法Word文件下载.docx(19页珍藏版)》请在冰豆网上搜索。
constmax=10;
vara:
array[1..max]oflongint;
i:
longint;
procedureqsort(l,r:
longint);
vari,j,x,y:
longint;
begin
=l;
j:
=r;
x:
=a[(l+r)div2];
repeat
whilea[i]<
xdoinc(i);
whilex<
a[j]dodec(j);
ifi<
=jthen
y:
=a[i];
a[i]:
=a[j];
a[j]:
=y;
inc(i);
dec(j);
end;
untili>
j;
ifl<
jthenqsort(l,j);
rthenqsort(i,r);
begin
randomize;
fori:
=1tomaxdobegina[i]:
=random(1000);
write(a[i]:
6);
end;
writeln;
qsort(1,max);
=1tomaxdowrite(a[i]:
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;
=1tomdon:
=n*2;
k:
matchlist[1,1]:
half:
whilek<
=mdo
=1tohalfdo{构造右上角方阵}
forj:
=1tohalfdomatchlist[i,j+half]:
=matchlist[i,j]+half;
=1tohalfdo{对称交换构造下半部分方阵}
=1tohalfdo
matchlist[i+half,j]:
=matchlist[i,j+half];
matchlist[i+half,j+half]:
=matchlist[i,j]
=half*2;
=k+1
=1tondo
=1tondowrite(matchlist[i,j]:
4);
writeln
[样例]
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<
t2,那么c:
=(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;
assign(input,’car.in’);
assign(output,’car.out’);
reset(input);
rewrite(output);
readln(s,a,b);
c0:
=0;
=s;
c:
=(c0+c1)/2;
t3:
=c/b;
{甲乘车到C的时间}
t1:
=t3+(s-c)/a;
{甲的总耗时}
t4:
=(c-t3*a)/(a+b);
{小车从C回头与乙相遇的时间}
t2:
=t3+t4+(s-(t3+t4)*a)/b;
{计算乙总耗时}
ift1<
t2thenc1:
=celsec0:
=c;
untilabs(t1-t2)<
zero;
writeln(t1);
close(input);
close(output)
例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:
binary:
array[1..32]oflongint;
assign(input,’mod.in’);
assign(output,’mod.out’);
readln(b,p,k);
{输入三个数}
len:
temp:
=p;
whiletemp<
>
0do{存放p的二进制转换}
=len+1;
binary[len]:
=tempmod2;
=tempdiv2
rest:
=lendownto1do{用二分法实现b^pmodk}
=rest*restmodk;
ifbinary[i]=1thenrest:
=bmodk*tempmodk{如果是奇数,就多乘b}
elserest:
=temp{否则就是rest*rest}
writeln(b,'
^'
p,'
mod'
k,'
='
rest);
{输出b^pmodk}
例5、麦森数(NOIP2003,mason.?
形如2P-1的素数称为麦森数,这时P一定也是个素数。
但反过来不一定,即如果P是个素数,2P-1不一定也是素数。
到1998年底,人们已找到了37个麦森数。
最大的一个是P=3021377,它有909526位。
麦森数有许多重要应用,它与完全数密切相关。
任务:
从文件中输入P(1000<
P<
3100000),计算2P-1的位数和最后500位数字(用十进制高精度数表示)。
[输入]
文件中只包含一个整数P(1000<
3100000)。
[输出]
第一行:
十进制高精度数2P-1的位数;
第2-11行:
十进制高精度数2P-1的最后500位数字(每行输出50位,共输出10行,不足500位时高位补0);
不必验证2P-1与P是否为素数。
mason.in
1279
mason.out
386
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
【算法分析】用对数的换底运算可以求出Log2P-1(10)的值,即为2P-1的位数。
而后500位的输出即为高精度乘法的运算,由于P最大可以达到3100000,若单使用循环,循环次数会很大,必然会运算超时。
遂采用二分算法,建立主运算过程Calc(p)和分运算过程Squa,Doub,其中Squa用于将数组中的数平方,Doub用于将数组中的数乘以2。
运算主过程即二分过程,主功能如下:
若P=1或P=2,则直接给存放数字的数组最低位赋值,否则再判断P
若参数P是奇数,则Calc(p)等价于Calc(pDiv2);
Squa;
Dou
若参数P是偶数,则Calc(p)等价于Calc(pDiv2);
Squa
递归运算结束后,不要忘记为数组最低位减去1,由于2的正整数次幂个位数字只会是2或4或6或8,遂直接将最低位减1即可,不必考虑退位。
ProgramMason(Input,Output);
Var
a:
Array[1..500]OfLongint;
i,j,k,l,m,n,p:
Longint;
ProcedureSqua;
b:
Array[1..1000]OfLongint;
i,j,k,jw:
Begin
Fori:
=1To1000Dob[i]:
=1To500Do
Begin
jw:
Forj:
b[i+j-1]:
=b[i+j-1]+a[i]*a[j]+jw;
=b[i+j-1]Div10;
=b[i+j-1]Mod10;
End;
b[i+j]:
=b[i+j]+jw;
=1TO500Doa[i]:
=b[i];
End;
ProcedureDoub;
i,j,jw:
=a[i]*2+jw;
=a[i]Div10;
=a[i]Mod10;
ProcedureCalc(p:
Longint);
Ifp>
=3Then
IfpMod2=0ThenBeginCalc(pDiv2);
IfpMod2=1ThenBeginCalc(pDiv2);
Doub;
End
Else
Ifp=1Thena[1]:
=2;
Ifp=2Thena[1]:
=4;
Assign(Input,'
mason.in'
Reset(Input);
Readln(p);
Close(Input);
Assign(Output,'
mason.ou'
Rewrite(Output);
Writeln(Trunc(Ln
(2)/Ln(10)*p)+1);
Calc(p);
a[1]:
=a[1]-1;
=500Downto1Do
Write(a[i]);
IfiMod50=1ThenWriteln;
Close(Output);
End.
例6、黑白棋子的移动(chessman.?
有2n个棋子(n≥4)排成一行,开始位置为白子全部在左边,黑子全部在右边,如下图为n=5的情形:
○○○○○●●●●●
移动棋子的规则是:
每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。
每次移动必须跳过若干个棋子(不能平移),要求最后能移成黑白相间的一行棋子。
如n=5时,成为:
○●○●○●○●○●
编程打印出移动过程。
chessman.in
7
chessman.out
step0:
ooooooo*******--
step1:
oooooo--******o*
step2:
oooooo******--o*
step3:
ooooo--*****o*o*
step4:
ooooo*****--o*o*
step5:
oooo--****o*o*o*
step6:
oooo****--o*o*o*
step7:
ooo--***o*o*o*o*
step8:
ooo*o**--*o*o*o*
step9:
o--*o**oo*o*o*o*
step10:
o*o*o*--o*o*o*o*
step11:
--o*o*o*o*o*o*o*
我们先从n=4开始试试看,初始时:
○○○○●●●●
第1步:
○○○——●●●○●{—表示空位}
第2步:
○○○●○●●——●
第3步:
○——●○●●○○●
第4步:
○●○●○●——○●
第5步:
——○●○●○●○●
如果n=5呢?
我们继续尝试,希望看出一些规律,初始时:
○○○○——●●●●○●
○○○○●●●●——○●
这样,n=5的问题又分解成了n=4的情况,下面只要再做一下n=4的5个步骤就行了。
同理,n=6的情况又可以分解成n=5的情况,……,所以,对于一个规模为n的问题,我们很容易地就把他分治成了规模为n-1的相同类型子问题。
数据结构如下:
数组c[1..max]用来作为棋子移动的场所,初始时,c[1]~c[n]存放白子(用字符o表示),c[n+1]~c[2n]存放黑子(用字符*表示),c[2n+1],c[2n+2]为空位置(用字符—表示)。
最后结果在c[3]~c[2n+2]中。
programchessman(input,output);
constmax=100;
varn,st,sp:
arr