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.
例5、麦森数(NOIP2003,mason.?
?
?
)
[问题描述]
形如2P-1的素数称为麦森数,这时P一定也是个素数。
但反过来不一定,即如果P是个素数,2P-1不一定也是素数。
到1998年底,人们已找到了37个麦森数。
最大的一个是P=3021377,它有909526位。
麦森数有许多重要应用,它与完全数密切相关。
任务:
从文件中输入P(1000
[输入]
文件中只包含一个整数P(1000
[输出]
第一行:
十进制高精度数2P-1的位数;
第2-11行:
十进制高精度数2P-1的最后500位数字(每行输出50位,共输出10行,不足500位时高位补0);
不必验证2P-1与P是否为素数。
[输入输出样例]
mason.in
1279
mason.out
386
00000000000000000000000000000000000000000000000000
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;
Var
b:
Array[1..1000]OfLongint;
i,j,k,jw:
Longint;
Begin
Fori:
=1To1000Dob[i]:
=0;
Fori:
=1To500Do
Begin
jw:
=0;
Forj:
=1To500Do
Begin
b[i+j-1]:
=b[i+j-1]+a[i]*a[j]+jw;
jw:
=b[i+j-1]Div10;
b[i+j-1]:
=b[i+j-1]Mod10;
End;
b[i+j]:
=b[i+j]+jw;
End;
Fori:
=1TO500Doa[i]:
=b[i];
End;
ProcedureDoub;
Var
i,j,jw:
Longint;
Begin
jw:
=0;
Fori:
=1To500Do
Begin
a[i]:
=a[i]*2+jw;
jw:
=a[i]Div10;
a[i]:
=a[i]Mod10;
End;
End;
ProcedureCalc(p:
Longint);
Begin
Ifp>=3Then
Begin
IfpMod2=0ThenBeginCalc(pDiv2);Squa;End;
IfpMod2=1ThenBeginCalc(pDiv2);Squa;Doub;End;
End
Else
Begin
Ifp=1Thena[1]:
=2;
Ifp=2Thena[1]:
=4;
End;
End;
Begin
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;
Fori:
=500Downto1Do
Begin
Write(a[i]);
IfiMod50=1ThenWriteln;
End;
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呢?
我们继续尝试,希望看出一些规律,初始时:
○○○○○●●●●●
第1步:
○○○○——●●●●○●
第2步:
○○○○●●●●——○●
这样,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:
integer;
c:
arr