540 likes | 699 Views
高精度运算. 转换数据类型. 加法运算. 减法运算. 乘法运算. 除法运算. 改善高精度运算的效率. 数据类型的转换. 在变量运算对象的数值范围为任何数据类型所无法容纳的情况下 , 采用整数数组(每一个元素对应一位十进制数,由其下标顺序指明位序号)。 1 、采用数串形式输入,并将其转化为整数数组。 2 、该数组的运算规则如同算术运算。 3 、用一个整数变量记录数据的实际长度(即数组的元素个数). 数据结构与转换方法. type numtype= array[1..500]of word ; { 整数数组类型 } var
E N D
转换数据类型 加法运算 减法运算 乘法运算 除法运算 改善高精度运算的效率
数据类型的转换 在变量运算对象的数值范围为任何数据类型所无法容纳的情况下,采用整数数组(每一个元素对应一位十进制数,由其下标顺序指明位序号)。 1、采用数串形式输入,并将其转化为整数数组。 2、该数组的运算规则如同算术运算。 3、用一个整数变量记录数据的实际长度(即数组的元素个数)
数据结构与转换方法 type numtype= array[1..500]of word;{ 整数数组类型} var a,b:numtype; {a和b为整数数组} la,lb:integer;{整数数组a的长度和b的长度} s:string; {输入数串} 将数串s转化为整数数组a的方法如下: k←length(s); for i←1 to k do a[k-i+1]←ord(s[i])-ord(‘0’);
加法运算c←a+b(a、b、c为numtype类型) var a,b,c:array[1..201] of 0..9; n:string; lena,lenb,lenc,i,x:integer; begin write('Input augend:'); readln(n);lena:=length(n); for i:=1 to lena do a[lena-i+1]:=ord(n[i])-ord('0');{加数放入a数组} write('Input addend:'); readln(n); lenb:=length(n); for i:=1 to lenb do b[lenb-i+1]:=ord(n[i])-ord('0');{被加数放入b数组} i:=1; while (i<=lena) or(i<=lenb) do begin x := a[i] + b[i] + x div 10; {两数相加,然后加前次进位} c[i] := x mod 10; {保存第i位的值} i := i + 1 end; if x>=10 {处理最高进位} then begin lenc:=i; c[i]:=1 end else lenc:=i-1; for i:=lenc downto 1 do write(c[i]); writeln {输出结果} end.
求回文数 若一个数(首位不为零)从左向右读与从右向左读都是一样,我们就将其称之为回文数。例如:给定一个10进制数56,将56加65(即把56从右向左读),得到的121是一个回文数。又如,对于10进制数87: STEP1:87+78=165 STEP2:165+561=726 STEP3:726+627=1353 STEP4:1353+3531=4884 在这里的一步是指进行了一次N进制的加法,上例最少用了4步得到回文数4884。 写一个程序,给定一个N(2≤N≤10,N=16)进制数m,m的位数上限为20。求最少经过几步可以得到回文数。如果在30步以内(包括30步)不可能得到回文数,则输出“impossible” 样例: INPUT OUTPUT N=9 m=87 STEP=6
1.将数串s转化为整数数组m 设数串s=s1‥sp,串长为p。其中si为第p-i+1位n进制数(1≤i≤p)。我们将s转化为整数数组m=m[p]‥m[1],其中m[i]对应第i位n进制数。 type mtype=array[1..100]of integer; var m:mtype; 按下述方法将s转化为整数数组m: p←length(s);{计算s的串长} for i←1 to p do {从最高位开始计算整数数组m } begin k←p-i+1;{计算si对应于的m数组下标} case s[i] of{转换si} ’a’..’f’:m[k]←10+ord(s[i])-ord(’a’); ’0’..’9’:m[k]←ord(s[i])-ord(’0’); else 输出错误信息并退出程序; end;{case} end;{for}
2.判别整数数组m是否为回文数 function check (m: mtype) :boolean;{若整数数组m为回文数,则返回true,否则返回false} var i:integer; begin check←false; for i←1to do if m[i] ≠m[p-i+1] then exit;{返回m非回文数标志} check←true;{返回m为回文数标志} end;{check}
3.n进制加法运算 整数数组m1与其反序数m2进行n进制加法运算,得到结果m1 procedure solve(var m1: mtype); var m2: mtype; begin for i←1 to p do m2[i]←m1[p-i+1];{计算反序数m2} for i←1 to p do {由右而左逐位相加} begin m1[i]←m1[i]+m2[i];m1[i+1]← ;{进位} m1[i]←m1[i]mod n;{确定当前位} end;{for} if m1[p+1] ≠0 then p←p+1;{最高位进位} if check (m1)then 输出步数并退出程序; end;{solve}
4.主程序 输入进制数n和数串s; fillchar(m,sizeof(m),0) 将s转换为整数数组m; if check(m) then begin 输出步数0;halt;end;{then} 步数初始化为0; while 步数≤30 do begin 步数+1;solve(m);end;{while} 输出无解信息;
减法运算c←a-b(a、b、c为numtype类型) var a,b,c:array[1..200] of 0..9; n,n1,n2:string; lena,lenb,lenc,i,x:integer; begin write('Input minuend:'); readln(n1); write('Input subtrahend:'); readln(n2); if (length(n1)<length(n2)) or (length(n1)=length(n2)) and (n1<n2) then begin {n1<n2,结果为负数} n:=n1;n1:=n2;n2:=n; write('-') end; lena:=length(n1); lenb:=length(n2); for i:=1 to lena do a[lena-i+1]:=ord(n1[i])-ord('0'); for i:=1 to lenb do b[lenb-i+1]:=ord(n2[i])-ord('0'); i:=1; while (i<=lena) or(i<=lenb) do begin if a[i]<b[i]then begin a[i+1]:= a[i+1] -1;a[i]:=a[i]+10; end;{向高位借位处理} c[i] := a[i]-b[i] ; {保存第i位的值} i := i + 1 end; lenc:=i; while (c[lenc]=0) and (lenc>1) do dec(lenc); {最高位的0不输出} for i:=lenc downto 1 do write(c[i]); writeln end.
高精度除法x←x/y(被除数x和除数y为整数) 高精度运算不仅能够扩大整数的值域,而且能够通过扩大小数的位数减低除法的精度误差,甚至还可以计算出循环节。设 设小数部分的位数上限为limit;小数部分的指针为len;st为循环节的首指针;s为小数序列,其中s[i]为第i位小数;posi为余数的位序列,其中posi[x]表示余数为x的位序号。 记下第1位的余数和小数(posi[x mod y]←1,s[1]←(x mod y)*10 div y,x←x mod y);然后按照除法的运算规则计算第2位、第3位的余数和小数‥‥。若下一位余数先前出现过(posi[x]<>0),则先前出现的位置posi[x]为循环节的开始(st←posi[x]),退出计算过程;否则依次类推,直至小数位数达到上限limit为止。
fillchar(s,sizeof(s),0);{小数部分初始化} fillchar(posi,sizeof(posi),0); {小数值的位序列初始化} len←0;st←0; {小数部分的指针和循环节的首指针初始化} read(x,y);{读被除数和除数} write(x div y);{输出整数部分} x←x mod y;{计算x除以y的余数} if x=0 then exit;{若x除尽y,则成功退出} while len<limit do{若小数位未达到上限,则循环} begin inc(len);posi[x]←len;{记下当前位小数,计算下一位小数和余数} x←x*10; s[len]←x div y;x←x mod y; if posi[x]<>0 {若下一位余数先前出现过,则先前出现的位置为循环节的开始} then begin st←posi[x]; break;end;{then} if x=0 then break; {若除尽,则成功退出} end;{while}
if len=0 then begin writeln;exit;end;{若小数部分的位数为0,则成功退出;否则输出小数点} write('.'); if st=0 {若无循环节,则输出小数部分,否则输出循环节前的小数和循环节} then for i←1 to len do write(s[i]) else begin for i←1 to st-1 do write(s[i]); write('('); for i←st to len do write(s[i]); write(')'); end;{else}
整数i乘多精度数组a 设x为当前位乘积和进位 x:=x+a[j]*i; a[j]:=x mod 10; x:=x div 10
精确计算n的阶乘n!(7<n<50) • 因为 50!<5050<10050=(102)50=10100 • 所以 50!可以用100个数组元素a[1],a[2],…,a[100]来存放,一个数组元素存放一个数位上的数字。 j 100 99 98 … 3 2 1 X a a[1],a[2],…,a[max]的值可以是0到9的任意数字 • 用i表示阶乘中的整数,取值范围在1至50之间, j为数组变量a的下标,取值范围在1至100之间,X存放来自低位的进位数。
const max=100; n=20; var a:array[1..max]of 0..9; i,j,k;x:integer; begin k:=1; a[k]:=1;{a=1} for i:=2 to n do{a*2*3….*n} begin x:=0;{进位初始化} for j:=1 do k do{a=a*i} begin x:=x+a[j]*i; a[j]:=x mod 10;x:=x div 10 end; while x>0 do {处理最高位的进位} begin k:=k+1;a[k]:=x mod 10;x:=x div 10 end end; writeln; for i:=k dowento 1 write(a[i]){输出a} end.
乘法运算c←a*b(a、b为numtype类型) 1、积的位数为la+lb-1或者la+lb; 2、如果暂且不考虑进位关系,则ai*bj应该累加在积的第j+i-1位上: x:= a[i]*b[j]+ x div 10+ c[i+j-1]; c[i+j-1] := x mod 10; 3、可以先乘、后处理进位
var a,b,c:array[1..200] of 0..9; n1,n2:string; lena,lenb,lenc,i,j,x:integer; begin write('Input multiplier:'); readln(n1); rite('Input multiplicand:'); readln(n2); lena:=length(n1); lenb:=length(n2); for i:=1 to lena do a[lena-i+1]:=ord(n1[i])-ord('0'); for i:=1 to lenb do b[lenb-i+1]:=ord(n2[i])-ord('0'); for i:=1 to lena do begin x:=0; for j:=1 to lenb do{对乘数的每一位进行处理} begin x := a[i]*b[j]+x div 10+c[i+j-1];{当前乘积+上次乘积进位+原数} c[i+j-1]:=x mod 10; end; c[i+j]:= x div 10;{进位} end; lenc:=i+j; while (c[lenc]=0) and (lenc>1) do dec(lenc); {最高位的0不输出} for i:=lenc downto 1 do write(c[i]); writeln end.
麦森数 【问题描述】形如2P-1的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,2P-1不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。 任务:从文件中输入P(1000<P<3100000),计算2P-1的位数和最后500位数字(用十进制高精度数表示) 【输入格式】文件中只包含一个整数P(1000<P<3100000) 【输出格式】第一行:十进制高精度数2P-1的位数;第2-11行:十进制高精度数2P-1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0) 不必验证2P-1与P是否为素数。
使用倍增思想,优化幂运算 首先,看一个简单的例子——已知整数a,计算a17。很显然,一种最简单的方法就是令b=a,然后重复16次进行操作b=b*a。这样,为了得到a17,共进行了16次乘法。 现在考虑另外一种方法,令a0=a,a1=a2,a2=a4,a3=a8,a4=a16,可以看出,ai=ai-12,(1≤i≤4)。于是,得到a0,a1,a2,a3,a4共需要4次乘法。而a17= a*a16=a0*a4, 也就是说,再进行一次乘法就可以得到a17。这样,总共进行5次乘法就算出了a17。
已知a,计算an: 将n表示成为二进制形式,并提取出其中的非零位,即n=2(b1)+2(b2)+……+2(bw), (2(i)=2i)不妨设b1<b2<……<bw。例如 n=9,b2=3,b1=0 由于已知a,所以也就知道了a0,重复bw次将这个数平方并记录下来,就可以得到(bw+1)个数:a2(0), a2(1),a2(2),……,a2(bw); 根据幂运算的法则,可以推出an= a2(b1)+2(b2)+..2(bw), =a2(b1)*a2(b2)*……*a2(bw),而这些数都已经被求出,所以最多再进行(bw+1)次操作就可以得到。
算法分析 1、2P-1的位数为 2、采用高精度运算计算和输出2P-1的最后500位数字。设 ans为2p-1对应的高精度数组; 我们将p转换为对应的二进制数Dn…D0,其中Di的权为2i。 = =( )2。将p对应二进制数中值为1的权2i作为2的次幂,组成ans=2P-1的一项( ),显然,后一项为前一项的平方。当前项存储在高精度数组I中,取后500位。 p= ans=2p-1= -1= -1。 我们将p对应二进制数中值为1的每一项连乘起来,每一次的乘积取后500位,最后的乘积ans-1即为2p-1对应的高精度数组。
assign(input,inp);reset(input);{输入文件读准备} assign(output,out);rewrite(output);{输出文件写准备} read(p);{输入2的乘幂数} writeln(trunc(p*ln(2)/ln(10)+1));{计算和输出2p-1的位数} fillchar(l,sizeof(l),0);l[0]:=2;{当前项初始化} fillchar(ans,sizeof(ans),0); ans[0]:=1;{乘积项初始化} while p>0 do{由低位向高位方向逐位分析p的每一个二进制位} begin 主程序
if p mod 2=1 then{若p的当前二进制位为1,则连乘当前项,取乘积的后500位} begin ans←ans*l;end;{then} p:=p div 2;{处理高一位} l←l2; end;{while} dec(ans[0]);{计算2p-1} for i:=499 downto 0 do{按照50位数一行的格式输出2p-1的后500位数} begin write(ans[i]); if i mod 50=0 then writeln; end;{for}
计算ans←ans*l fillchar(ans1,sizeof(ans1),0);{乘积初始化} for i:=0 to 499 do{连乘当前项} for j:=0 to 499-i do inc(ans1[i+j],ans[i]*l[j]); for i:=0 to 498 do{对乘积进行规整} begin inc(ans1[i+1],ans1[i]div10); ans1[i]:=ans1[i] mod 10; end;{for} ans1[499]:=ans1[499] mod 10; ans:=ans1;
计算l←l2 fillchar(l1,sizeof(l1),0);{高一位的当前项初始化} for i:=0 to 499 do{前一项的平方作为当前项,取后500位} for j:=0 to 499-i do inc(l1[i+j],l[i]*l[j]); for i:=0 to 498 do{对当前项进行规整} begin inc(l1[i+1],l1[i] div 10); l1[i]:=l1[i] mod 10; end;{for} l1[499]:=l1[499] mod 10; l:=l1;
循环 • 【问题描述】 • 乐乐是一个聪明而又勤奋好学的孩子。他总喜欢探求事物的规律。一天,他突然对数的正整数次幂产生了兴趣。 • 众所周知,2的正整数次幂最后一位数总是不断的在重复2,4,8,6,2,4,8,6……我们说2的正整数次幂最后一位的循环长度是4(实际上4的倍数都可以说是循环长度,但我们只考虑最小的循环长度)。类似的,其余的数字的正整数次幂最后一位数也有类似的循环现象:
这时乐乐的问题就出来了:是不是只有最后一位才有这样的循环呢?对于一个整数n的正整数次幂来说,它的后k位是否会发生循环?如果循环的话,循环长度是多少呢?这时乐乐的问题就出来了:是不是只有最后一位才有这样的循环呢?对于一个整数n的正整数次幂来说,它的后k位是否会发生循环?如果循环的话,循环长度是多少呢? 注意: 1. 如果n的某个正整数次幂的位数不足k,那么不足的高位看做是0。 2. 如果循环长度是L,那么说明对于任意的正整数a,n的a次幂和a + L次幂的最后k位都相同。 【输入文件】输入文件circle.in只有一行,包含两个整数n(1≤n<10100)和k(1≤k≤100),n和k之间用一个空格隔开,表示要求n的正整数次幂的最后k位的循环长度。 【输出文件】输出文件circle.out包括一行,这一行只包含一个整数,表示循环长度。如果循环不存在,输出-1。
问题的规模要求采用高精度计算 • 本题要求计算n(1≤n<10100)的正整数次幂的最后k(1≤k≤100)位的循环长度,因此必须采用k位高精度运算。不仅每次乘幂运算需要通过高精度乘法保留后k位,而且由于最小循环长度是任何整数类型无法容纳的,因此其计算过程也需要用到k位高精度加法。
必须采用倍增思想提高时效 • 如果采用简单的连乘运算,是根本不可能满足时效要求的,只能采用倍增思想逐位递推:先保证第1位循环,求出最小的循环长度l1;接下来计算第2位循环的最小循环长度:连乘 ,直至出现2位循环为止。此时得出2位的最小循环长度l2;然后连乘 ,直至出现3位循环为止,由此得出第3位循环的最小循环长度;……,依次类推,直至递推出第k位的最小的循环长度lk。在计算第i(1≤i≤k)位循环时,如果连乘了10次后没有结果,则输出无解信息。因为第i位所有可能出现的数字都已经出现,因此不可能递推出长于i位的循环节了。
数据结构 type ptype=array[1..200]of longint; ttype=array[1..200]of integer; var p,tempp,stp,pp:ptype;{参与运算的两个操作数为p和tempp } tempt,t:ttype;{ 循环长度为tempt;连乘一次增加的次幂数为t } k,tott,tottemp:longint;{tempt的长度为tottemp;t的长度为tott}
输入信息,建立整数数组 readln(st); {读输入串} st1:=copy(st,1,pos(' ',st)-1); delete(st,1,pos(' ',st));val(st,k,ok);{从输入串中截出底数st1和正整数次幂的最后位数,将其转换为整数k} if length(st1)<k{若底数st1的长度不足k,则将其全部转换为整数数组p;否则将其低k位转换为整数数组p} then for i:=1 to length(st1) do p[length(st1)-i+1]:=ord(st1[i])-ord('0') else for i:=1 to k do p[i]:=ord(st1[length(st1)-i+1])-ord('0'); end;
计算循环长度 stp:=p; tempp:=p; tempt[1]:=0;tottemp:=1;{循环长度初始化}; while true do{先保证第1位循环,求出最小循环长度tempt[1]} begin inc(tempt[1]);pp:=p; {pp=ptempt[1]} mul(p,tempp);{k位运算:p←p*tempp} if check(stp,p,1) then break{若p 的第1位出现循环,则退出while } end;{ while } t:=tempt;tott:=tottemp;{暂存tempt 及其长度} for kk:=2 to k do{依次递推第2位…第k位循环的最小循环长度} begin p:=stp; tempp:=pp;{暂存kk-1位运算的乘幂} fillchar(pp,sizeof(pp),0); pp[1]:=1;{pp←1} fillchar(tempt,sizeof(tempt),0); tottemp:=0;{ 最小循环长度tempt初始化为0} tot:=0;{ 相乘次数初始化}
while true do begin plus(tempt,t,tottemp,tott); {tempt←tempt+t} mul(p,tempp);mul(pp,tempp);{p←p*tempp,pp←pp*tempp,即pp=p/stp} if check(stp,p,kk) then break; {若p的第kk位出现循环,则退出while} tot:=tot+1;{累计相乘次数} if tot>10 then 输出-1{若连续乘了10次仍然没有出现循环,则输出无解信息。因为第kk位所有能出现过的数字都已经出现过了,仍没有我们想要的n的第kk位,就表示不可能循环了} end;{ while } tempp:=pp; {当前位乘幂pp暂存tempp} t:=tempt;tott:=tottemp{循环长度tempt暂存t} end;{ for } for i:=tott downto 1 do write(tempt[i]);{输出循环长度} writeln; end;
检查p和tempp的后kk位是否相同 function check(p,tempp:ptype;kk:longint):boolean;{若整数数组p 和tempp的后kk位相同,则返回true;否则返回false} var i:longint; begin check:=false; for i:=1 to kk do if p[i]<>tempp[i]then exit; check:=true end;{ check }
计算t←t+ tempt procedure plus(var t:ttype;tempt:ttype;var tott:longint;tottemp:longint);{t的长度为tott,tempt的长度为tottemp} var i,tt:integer; begin if tott>tottemp{计算运算的位数} then tt:=tott else tt:=tottemp; for i:=1 to tt do t[i]:=t[i]+tempt[i];{逐位相加} for i:=1 to tt do{每位规整为十进制数} begin t[i+1]:=t[i+1]+t[i] div 10;t[i]:=t[i] mod 10 end;{ for } while t[tt+1]>0 do{最高位的进位处理} begin t[tt+1]:=t[tt+1]+t[tt] div 10; t[tt]:=t[tt] mod 10; inc(tt) end; tott:=tt{返回和的位数} end;{ plus }
计算p←p*tempp, procedure mul(var p:ptype;tempp:ptype);{p的初始长度和tempp 的长度为k} var i,j:longint; temp:ptype; begin temp:=p; fillchar(p,sizeof(p),0);{积初始化} for i:=1 to k do{逐位相乘} for j:=1 to k do p[i+j-1]:=p[i+j-1]+temp[i]*tempp[j]; for i:=1 to k do{p的第k位…第1位规整为十进制数} begin p[i+1]:=p[i+1]+p[i] div 10;p[i]:=p[i] mod 10 end;{ for } for i:=k+1to2*k-1 do p[i]:=0{p的第k+1位…第2*k-1位清零} end;
改善高精度运算的效率 用一个整型数组来表示一个很大的数,数组中的每一个数表示一位十进制数字。这种方法的缺点是,如果十进制数的位数很多,则对应数组的长度会很长,并增加了高精度计算的时间。改善高精度运算效率的两种方法 ⑴扩大进制数 ⑵建立因子表一、扩大进制数用一个Longint记录4位数字是最佳的方案。那么这个数组就相当于一个10000进制的数,其中每一个元素都是10000进制下的一位数。1、数据类型type numtype=array[0..9999] of longint;{整数数组类型,可存储40000位十进制数} var a,n: numtype;{a和n为10000进制的整数数组}st:string; {数串}la,ln integer; {整数数组a和n的长度}
2、整数数组的建立和输出当输入数串st后,我们从左而右扫描数串st,以四个数码为一组,将之对应的10000进制数存入n数组中。具体方法如下:readln(st);{输入数串st}k←length(st);{取得数串st的长度} for i←0 to k-1 do begin {把st对应的整数保存到数组n中} j←(k-i+3) div 4-1;n[j]←n[j]*10+ord(st[i+1])-48;end;{for} ln←(k+3) div 4;当得出最后结果a后,必须按照由次高位(a[la-2])到最低位(a[0])的顺序,将每一位元素由10000进制数转换成10进制数,即必须保证每个元素对应四位10进制数。例如a[i]=0015(0≤i≤la-2),对应的10进制数不能为15,否则会导致错误结果。我们按照如下方法输出a对应的10进制数:write(a[la-1]); {输出结果} for i←la-2 downto 0 do write(a[i] div 1000,(a[i]div 100)mod 10,(a[i]div 10)mod 10,a[i]mod 10);
3、介绍几个基本运算 ⑴整数数组-1(n←n-1,n为整数数组) 我们从n[0]出发往左扫描,寻找第一个非零的元素(n[j]≠0,n[j-1]=n[j-2]=…=n[0]=0)。由于该位接受了底位的借位,因此减1,其后缀全为9999(n[j]= n[j]-1,n[j-1]=n[j-2]=…=n[0]=9999)。如果最高位为0(n[ln]=0),则n的长度减1。 j←0;{从n[0]出发往左扫描,寻找第一个非零的元素} while (n[j]=0) do inc(j); dec(n[j]);{由于该位接受了底位的借位,因此减1} for k←=0 to j-1 do n[k]←9999;{其后缀全为9999} if ((j=ln-1)and(n[j]=0)) then dec(ln);{如果最高位为0,则n的长度减1}
⑵整数数组除以整数(a←a/i,a为整数数组,i为整数)⑵整数数组除以整数(a←a/i,a为整数数组,i为整数) 我们按照由高位到底位的顺序,逐位相除。在除到第j位时,该位在接受了来自j+1位的余数(a[j]←a[j]+(j+1位相除的余数)*10000)后与i相除。如果最高位为0(n[ln]=0),则n的长度减1。 l←0;{余数初始化} for j←la-1 downto 0 do begin{按照由高位到底位的顺序,逐位相除} inc(a[j],l*10000);{接受了来自第j+1位的余数} l←a[j] mod i;{计算第j位的余数} a[j] ←a[j] div i;{计算商的第j位} end;{for} while (a[la-1]=0) do dec(la);{计算商的有效位数}
⑶两个整数数组相乘(a←a*n,a和n为为整数数组)⑶两个整数数组相乘(a←a*n,a和n为为整数数组) 我们按照由高位到底位的顺序,将a数组的每一个元素与n相乘。当计算到a[j]*n时,根据乘法规则,a[j-1], …,a[0]不变,a[j]为a[j]为a[j]与n[0]的乘积,a[j+k]加上a[j]*n[k]的乘积(k=ln-1,ln-2, …,1),然后按照由底位到高位的顺序处理进位。最后,如果a[la-1]*n有进位,则乘积a的有效位数为la+ln;否则a的有效位数为la+ln-1。 for j←la-1 downto 0 do {a1←a*n} for k←ln-1 downto 0 do inc(a1[j+k-1],a[j]*n[k]); a←a1; l←0; {进位初始化} for j←0 to la+ln-1 do begin {按照由底位到高位的顺序处理进位} inc(l,a[j]);{计算经过进位后的第j位} a[j] ←l mod 10000; {将第j位规整为10000进制数} l←l div 10000;{计算进位} end;{for} if (a[la+ln-1]<>0 ) then inc(la,ln) {修改有效位数} else inc(la,ln-1);
彩票 现今,社会上流行着各种各样的福利彩票,彩票已经融入到了人们的日常生活之中。彩票之所以能吸引那么多的人们,玩法多是一大原因。其中有一类是从前N个自然数中选出M个(不计顺序)不同的号码,如果这M个号码与摇奖时摇出的M个中奖号码完全相符,那么就中了头奖。如现在已经有的:30选7,35选7,36选7,37选7…… 随着时间的推移,越来越多的人不满足于原来的玩法。为了追求更大的刺激,可供选择的号码和每注的号码个数越来越大,88选8,518选18,8888选68等等应运而生。但是,由此也衍生出了许多麻烦。由于数字越来越大,福彩中心的工作人员们已经无法用一般的计数器精确地计算出每一种彩票中头奖的概率。现在请你帮助他们,编一个程序:对于每一种玩法,能够快速准确地计算出中头奖概率的倒数。 输入: n (MN<1040) m (0<M1000) 输出: 在“N选M”的玩法中,中头奖的概率的倒数。
从N个数中(不计顺序)取出M个不同的数的取法共有C(N, M)种。这里C(N, M)表示组合数。因此,要使摇出的中奖号码与所选的号码完全相同,概率只有1 / C(N, M)。所以我们要求的值即为C(N, M)。根据组合数的计算公式: 我们可以直接地求解。但是由于题目中的N可能很大,所以我们必须要用到高精度计算。而在高精度计算中,运行的时间与参与运算的数的大小有直接的关系。所以,我们要使运算的中间结果尽可能地小。如果我们先把N~(N-M+1)这M个连续的自然数乘起来,再依次除以1~M就是一种不太明智的选择。
1.我们可以先乘N除1,然后乘(N-1)除2,再乘(N-2)除3,……最后乘(N-M+1)除M。因为连续的K个自然数的积一定能被K!整除,所以在这一过程中不会出现除不尽的情况。同时也使得中间结果比较小,从而提高了程序运行的速度。1.我们可以先乘N除1,然后乘(N-1)除2,再乘(N-2)除3,……最后乘(N-M+1)除M。因为连续的K个自然数的积一定能被K!整除,所以在这一过程中不会出现除不尽的情况。同时也使得中间结果比较小,从而提高了程序运行的速度。 2.用一个Longint记录4位数字。数组相当于一个10000进制的数,其中每一个数都是10000进制下的一位数。
readln(st); readln(m); k:=length(st);{取得数串st的长度} for i:=0 to k-1 do begin {把N保存到数组n中} j:=(k-i+3) div 4-1;n[j]:=n[j]*10+ord(st[i+1])-48; end;{for} ln:=(k+3) div 4; a[0]:=1 ;la:=1; {初始化数组a} for i:=1 to m do begin {计算组合数C(n,m)} for j:=la-1 downto 0 do begin {将n乘到a上} for k:=ln-1 downto 1 do inc(a[j+k],a[j]*n[k]); a[j]:=a[j]*n[0]; end;{for} l:=0; for j:=0 to la+ln-1 do begin {处理进位} inc(l,a[j]);a[j]:=l mod10000 ;l:=l div 10000; end;{for}
if (a[la+ln-1]<>0) {修改有效位数} then inc(la,ln) else inc(la,ln-1); l:=0; {将a除以i} for j:=la-1 downto 0 do begin inc(a[j],l*base);l:=a[j] mod i;a[j]:=a[j] div i; end;{for} while (a[la-1]=0) do dec(la); j:=0; {n减去1} while (n[j]=0) do inc(j); dec(n[j]); for k:=0 to j-1 do n[k]:=10000-1; if ((j=ln-1)and(n[j]=0)) then dec(ln); end;{for} write(a[la-1]); {输出结果} for i:=la-2 downto 0 do write(a[i] div 1000,(a[i]div 100)mod 10,(a[i]div 10)mod 10,a[i]mod 10);
二、建立因子表 任何自然数都可以表示为n=p1k1*p2k2*……*ptkt的形式,p1,p2,……,pt为质因子。设num数组为自然数n,其中num[i]为因子i的次幂数(1≤i≤k)。显然,num[k], num[k-1]……, num[2]构成了一个自然数,该自然数可以用十进制整数数组ans存储。ans的计算过程如下 ans[0] ← 1;{将num转换为ans} for i←2 to k do {枚举每一个因子} for j←1 to num[i] do multiply (ans,i);{连乘num[i]个因子i} multiply的过程为高精度十进制运算中的乘法运算(ans←ans*I) 有了自然数n的因子表num,可以十分方便地进行乘法或除法运算。例如整数x含有k个因子i,经过乘法或除法后,
我们按照上述方法依次处理x的每一个因子,得出的num即为积或商我们按照上述方法依次处理x的每一个因子,得出的num即为积或商 procedure add(x,ob:longint);{ob=1,num←num*x;ob=-1,num←num/x} var i:longint; Begin for i←2 to x do {搜索x的每一个因子i} while (x mod i =0) do {计算x含因子i的个数k。 num[i]= } begin inc(num[i],ob);x ← x div i; end;{while} end;{add} 显然,如果n1=x1*x2*…*xk,则可以通过连续调用 add(x1,1);add(x2,1);…;add(xk,1); 得出n1对应的因子表num。如果n2=1/(x1,x2…xk),则可以通过连续调用 add(x1,-1);add(x2,-1);…;add(xk,-1); 得出n2对应的因子表num。注意,初始时num数组清零。