由于是自己的研究成果,不免会有很多问题,如果发现问题,欢迎指正并和本人讨论。
摘要 :我们通常可以利用计算机的高效性求解一些我们难以自己解决的问题,但是毫无疑问的,计算机也有它的局限性。我们需要计算Mn mod p的值,但是当M、n的值比较大时,计算机可能计算不出来具体的结果,这样,进行算法的优化就至关重要了。下面对取余的问题进行深入探究。
计算机是个神奇的东西,我们通常可以利用计算机的高效性求解一些我们难以自己解决的问题,但是毫无疑问的,计算机也有它的局限性。其中一个重要的问题是计算机计算的效率问题,计算机的计算是完全靠指令控制的,那么一个好的算法所含解决的指令是以少为宜的。这样说来,一个好的算法无疑可以更有效率的解决问题。但是,对计算机来说,它是不懂得算法的,这样,一个好的算法多是基于人脑进行构造的。
下面我们进入正题,我们需要计算Mn mod p(M的n次方对p取余的值)。对于当M、n较小的时候,这是很容易的,事实上,只要计算机能够算出Mn的具体答案的话,再对p取余更不是什么难事。但是当M、n的值比较大时,计算机可能计算不出来具体的结果,但是通过一些简单的算法优化,我们却能间接的计算出我们想要的结果。下面我们对取余的问题进行深入探究。
一、必备的基础知识:
1.同余定理:
① (a + b) % p = ((a % p) + (b % p)) % p
② (a * b) % p = ((a % p) * (b % p)) % p
③ (a b) % p = ((a % p) b) % p
2.取余运算的基本性质:
①如果a ≡ b ( mod p ),c ≡ d ( mod p ),则 ( a / c ) ≡ ( b / d ) ( mod p )
②如果Mn mod p = t,那么可得 0 <= t < p
③如果Mn mod p = t,那么则存在一个k使得Mn = k * p + t ,且满足Mn / p = k
④如果Mn mod p = t,若满足Mn < p,则一定满足 t = Mn
二、引例
假设我们需要计算(33335555) % 10 的值,那么由同余定理③,即:
33335555 % 10 = ((3333 % 10)5555)% 10 = (3 5555) % 10
由于:3 4 = 81,81 % 10 = 1,5555 = 1338 * 4 + 3
所以:3 5555 = 3 1338 * 4 + 3 = 3 1388* 4 *33
所以:3 5555 % 10 = (3 1388 * 4 *33)% 10
由同余定理②,(3 1388 * 4 *33) % 10 = ((3 1388*4)% 10 * 3 3 % 10) % 10
= ((3 1388*4)% 10 * 3 3 % 10) % 10
= (((3 4)1388)% 10 * 3 3 % 10) % 10
= ((((3 4)% 10)1388) % 10 * 3 3 % 10) % 10
=((11388) %10 * 3 3 % 10) % 10
=(1 % 10 *9 % 10) % 10
=9 % 10
=9
综上所述:(33335555) % 10 = 9。
三、对引例的一般化:
我们希望计算出Mn % p ( p >= 1 )的值,根据上面的引例可以进行算法的猜想,按照以上步骤,我们将算法思路归纳如下:
(一)、首先Mn % p = (M % p)n % p,若记M’ = M % p,则由取余运算的基本性质②可知,0 <=M’ < p,则我们将问题转化为求(M’)n % p的值。
(二)、我们现在希望能找到一个x,使得(M’)x % p = 1,这样n一定可以写成ax+b的形式。对于计算机这貌似很容易办到,我们可以用一个for循环进行寻找,我们如果总能找到这样的x满足上述条件的话,我们就可以利用引例的方法求解。
int num=M’;
while( num*M’ % p ! = 0 )
num++;
这样我们得出的num是满足Mx % p != 0在一定范围内的x最大值,而我们需要的是满足Mx % p == 0 的最小值为num+1,即x=num+1;
(三)、假设(二)的结论成立的话,那么
(M’ a* x *M’b) % p = ((M’ a *x) % p * M’ b %p) % p
= ((M’ a*x) % p * M’ b % p) % p
= (((M’ x)a ) % p * M b % p) % p
= ((((M’ x)% p) a ) % p * M’ b % p) % p
=((1a) % p * M’ b % p) % p
=(1 % p *M’ b % p) % p\
(四)、注意M’ = M % p ,当M’ = 0 时,即M 能被p整除,此时Mn % p = 0,故这种情况我们不用考虑。
在以上条件都满足的条件下,此时,需要对p进行讨论
(1)当p = 1 时,任何数对p取余都得0 ,故这种情况没有讨论的必要,此时Mn % p = 0,这种情况我们也不过多讨论。
(2)当p > 1 时,由取余运算性质④,1 % p = 1,此时上式
=(M’b % p) % p
由取余运算基本性质②知0 <= M’b % p < p ,又由取余运算基本性质④,则易推知:(M’b % p) % p
(五)由取余运算③的逆性质,b = n % x (0 <= b < x)
即,最终结果Mn % p = M’b % p。
综上所述,我们理论上可以解出任意M、n、p时,Mn % p的值
四、问题的提出:
上述算法看似可行,但是其中还存在一些问题,为了算法逻辑的严密性,我们现在对其中的一些问题进行证明。
对x值得寻找问题。
由观察法我们发现8x % 14的值,无论x为任何满足 x >= 1 的任何值时,8x % 14= 8恒成立。即当M’ = 8 , p = 14时,Mn % p无论x为任何值( x >= 1 )的时候总是得8。而当x = 0 时,代表M’0 % p = 1,这很显然是恒成立的,所以讨论这种情况也没有意义,因为由上文可知当x=0时,b=0,所以M’b % p = 1 % p ( p > 1 ),故这种情况我们可以很快的得出结果M’b % p = 1;
但是当x!=0时,之前的算法就存在一个很大的硬伤,最起码当M’ = 8 , p = 14的时候,我们根本无法找到这样的x使得M’x % p = 1成立,所以我们需要对这种情况进行深入探究。
继续观察,我们发现7x % 14的值,无论x为任何满足 x >= 1 的任何值时,8x % 14= 7恒成立。
那么是否还存在其他的数使得M’x % p永远也找不到一个x使M’x % p = 1成立呢,所有的这种情况的数我们都需要单独讨论,下面对这种情况进行探讨(以下为方便起见,我们用M代替M’,请注意不要混淆):
假设M % p = t 成立,那么就有
M2 % p = (M % p)2 % p = t2 %p
M3 % p = (M % p)3 % p = t3 %p
……
同理可得
Mn % p = (M % p)n % p = tn %p
所以我们可以认为M = t ,又由于M % p = t ,则由取余运算基本性质④,t = M,所以我们可得结论Mx % p = M 对于任意x > 1恒成立。
我们用数学归纳法证明此结论:
(1)当x = 2 时,由上面的推导知M2 % p = M成立时,下面的归纳成立。
(2)假设当x = k时,Mx % p = M成立。
(3)当x = k + 1时
Mk+1% p = (M*Mk) % p
由同余定理②
=((M %p)*(Mk % p)) % p
=(M*M) % p
= M
故,只要满足条件M2 % p = M,这样的M、p就会使得Mx % p = M对于x > 1恒成立。
由取余运算性质③,存在k,使得M2 = k*p +M成立。
这样在M取,[1,p) 时,我们可以先判断一下M2 % p = M是否成立,当成立时则不可能找到这样的x 使得Mx % p = 1,即这样的x不存在。
但是由上面的推论过程,此时Mn % p = M,这样就将这种极特殊的情况处理了。
这样,除了这种情况后,我们总能找到这样的最小x值使得Mx % p = 1成立,即算法的逻辑连续性存在。
五、算法实现:
#include <stdio.h>
#define LL __int64
int main()
{
__int64M,n,p,x,mid,b;
while(scanf("%I64d%I64d%I64d",&M,&n,&p)!=EOF)
{
if(p==1)
{
printf("0\n");
continue;
}
if(n==0&&p>1)
{
printf("1\n");
continue;
}
if(n==1&&p>1)
{
printf("%I64d\n",M%p);
continue;
}
if(n>1&&p>1)
{
M=M%p;
if((M*M)%p==M)
{
printf("%I64d\n",M);
continue;
}
else
{
x=2;
mid=M;
while((mid*M)%p!=1)
{
mid=mid*M;
x++;
}
b=n%x;
mid=1;
while(b--)
mid=mid*M;
M=mid;
printf("%I64d\n",M%p);
}
}
}
return 0;
}
六、对该问题解法的优化探究(网络资料查阅)
通过查阅资料,我们发现一种对此问题的高效解法,那就是著名的快速幂。
(一)算法原理:
当n为偶数时,Mn % p = ( M*M )n/2 % p = (( M%p)2)n/2 % p
当n为奇数时,Mn % p = M*( M*M ) (n-1)/2 = (( M%p)*(( M % p)2 )(n-1)/2%p) % p
(二)算法实现:
(1)递归实现:
#idefine LL __int64
LL Mod_exp( LLa,LL b,LL c )
{
if( b==0 )
return 1;
else if( b & 1 )
return( a % c )*Mod_exp( a, (b-1)/2, c ) % c
else
return Mod_exp( a, b/2, c ) % c;
}
(2)迭代实现
#idefine LL __int64
LL Mod_exp( LL a, LL b, LL c )
{
LL ans=1;
while( b )
{
if( b & 1 )
ans = ans * a % c ;
a= ( a % c ) * (a % c) % c ;
b / = 2;
}
return ans;
}
(三)特别说明:
1.对于头文件,不同编译器可能不一样
2.if( b&1 ) 代表b为奇数
3.本代码来自某大牛fudq之手
七、 总结
取余问题不论在数学界还是计算机算法界都是热点问题,也是十分有趣的问题。寻找快速有效的解决这类问题的办法一直是我们努力的目标,要想找到解决问题的好算法,除了应该拥有雄厚的数学基础外,我们还应该多思考,在思考中寻觅我们需要的灵光,这样,相信,不管是什么问题,我们都能很好的解决它。
我坚信......!
参考资料:略