数数(count)——分解质因数or快速幂

【问题描述】

给定nmk都是小于等于10001的正整数,输出给定的n个数中,其m次幂能被k整除的数的个数。

【输入格式】

有两行组成,第一行是三个整数nmk

第二行是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.

这个代码的效率还好,反正不超时,能ac

50个数据,共3.8s,平均每个0.08s

(另注:暴力模拟算法不能ac,部分超时)

 

然后在思考后尝试了使用快速幂来解这道题

 

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次(预处理忽略的情况下)

类似的,使运算效率大大提高。

posted on 2011-10-16 17:41  codeway3  阅读(1388)  评论(0编辑  收藏  举报

导航