数数(count)——分解质因数or快速幂
【问题描述】
给定n,m,k都是小于等于10001的正整数,输出给定的n个数中,其m次幂能被k整除的数的个数。
【输入格式】
有两行组成,第一行是三个整数n,m,k
第二行是n个正整数 都不超过10001
【输出格式】
输出满足条件的数的个数
【样例输入】count.in
3 2 50
9 10 11
【样例输出】count.out
1
提供数据下载:count.rar
首先,我想到的是一种数学方法,
分解质因数,
1 program count;
2 var
3 i,j,k,l,n,z,m,ans,w,bb:longint;
4 a,b:array[1..10001]of longint;
5 begin
6 assign(input,'count.in');
7 reset(input);
8 assign(output,'count.out');
9 rewrite(output);
10 readln(n,m,k);
11 l:=k;
12 while k>1 do
13 begin
14 for i:=2 to l do
15 while k mod i=0 do
16 begin
17 inc(a[i]);
18 k:=k div i;
19 end;
20 for i:=2 to l do
21 if a[i]>0 then w:=i;
22 end;//对k分解因数,一定是质因数(因为循环因数从小到大,且一下子处理完)
23 //把k的因数种类、个数用数组存储
24 //w用来做个小标记,标记k的最大质因数,限制下面的循环层数
25 //已上过程写的比较随意,因为题中数据范围有限,枚举的边界我就没有特别处理来缩短时间
26 for i:=1 to n do
27 begin
28 read(z);
29 fillchar(b,sizeof(b),0);//b数组记录读入的数的质因数种类及个数
30 l:=z;
31 while z>1 do
32 begin
33 for j:=2 to l do
34 while z mod j=0 do
35 begin
36 inc(b[j]);
37 z:=z div j;
38 end;
39 end;//这部分的操作与上面对k的操作是相同的
40 bb:=1;//做标记,若为1则表示z能被k整除,若为0则不能
41 for j:=2 to w do
42 if b[j]*m<a[j] then bb:=0;//逐质因数判断是否能被整除
43 //这里要说明一下,整个程序的原理
44 {其实就是个初中级的数学推导
45
46 设,这里读入了一个整数z,
47 分解质因数为
48 z=x1*...*x1*x2*...*x2*x3*...*x3*........
49 各质因数个数分别记录为
50 a[1],a[2],a[3]......
51
52 设,先前读入的k,
53 分解为
54 k=y1*...*y1*y2*...*y2*..................
55 各质因数个数分别记录为
56 b[1],b[2]...........
57
58 若z^m能被k整除,则z^m=k*xx
59 (x1*...*x1*x2*...*x2*x3*...*x3*....)^m=(y1*...*y1*y2*...*y2*......)*xx
60 由题设上式可化为
61 (x1^a[1]*x2^a[2]*..................)^m=(y1^b[1]*y2^b[2]*..........)*xx
62 进一步的
63 (x1^(a[1]*m)*x2^(a[2]*m)*............)=(y1^b[1]*y2^b[2]*..........)*xx
64
65 由以上的推导
66 若存在一个数n既是z的质因数又是k的质因数
67 则a[n1]*m>=b[n2]
68 (a[n1]代表z分解n的个数,b[n2]代表k分解n的个数)
69
70 于是,有了上面的判断,
71 若XXX*m<XXX则标记为不能整除
72 }
73 if bb=1 then inc(ans);//若能被整除,则最终结果数加一
74 end;
75 writeln(ans);
76 close(input);
77 close(output);
78 end.
View Code
1 var
2 i,j,m,n,k:longint;
3 x,kk,print,ans:longint;
4 b:array[0..10000] of longint;
5 v:arraY[0..10000] of boolean;
6 begin
7 assign(input,'count.in');
8 reset(input);
9 assign(output,'count.out');
10 rewrite(output);
11
12 read(n,m,k);
13 kk:=trunc(ln(m)/ln(2))+1;
14
15 for i:=1 to kk do
16 if m>>(i-1) and 1=1 then v[i]:=true;
17
18 for i:=1 to n do
19 begin
20 read(x); b[1]:=x; ans:=1;
21
22 for j:=2 to kk do
23 b[j]:=b[j-1]*b[j-1] mod k;
24
25 for j:=1 to kk do
26 if v[j] then ans:=ans*b[j] mod k;
27 if ans=0 then inc(print);
28 end;
29 writeln(print);
30
31 close(input);
32 close(output);
33 end.
这个算法的效率就相当高了
50个点,0.5s
平均每个点0.01s
具体的,介绍一下快速乘幂的思想(个人理解)
利用了位运算预处理(可以不用,用递归也可以)
利用二进制位来表示乘幂的幂次数,
相当于乘的次数变为了原来的log2(n)次,效率大大提升,
而且代码也很好写。
对k取模很好理解,对于其他部分稍讲一下
具体举个例子,x=5,m=6,
执行程序得
kk=3
v[1] v[2] v[3]
0 1 1
b[1] b[2] b[3]
5 25 625
按流程,
ans=5*5*5*5*5*5
=1*25( mod k)*625( mod k){mod k 暂时忽略}
=5^6
预处理后乘的次数由至少5次到2次(预处理忽略的情况下)
类似的,使运算效率大大提高。