大数取模:一般取模+技巧取模+快速幂取模+欧拉函数(费马小定理)

转载自:https://blog.csdn.net/u011361880/article/details/77802742

 

一般取模运算(不推荐): 
(a^n)%m。 我们可以改写为(a^n)%m= ((a%m)^n)%m, 即循环n次。 
缺点:低效,循环了n次。

int exp_mod(int a,int n,int m){
    a = a%m;
    int temp = 1;
    while(n--)
    {
         temp = temp * a;
         temp = temp % m;
    }
    return temp;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

第一种,技巧取模: 
(a^n)%10 
当n非常大时,嗯,只能用字符串存n的时候。

简单分析一下, 
a%10. 有10种可能,(来源于室友“张博士”的逆天发现,明明可以靠脸吃饭,他偏偏要靠才华,明明可以快速幂取模,它偏要发现这种,出现大大大大数的)

a%10 = 0. 这个结果就不需要看了。0 
a%10 = 1. (1^n )%10会出现的可能数字:1 
a%10 = 2. (2^n )%10会出现的可能数字:2,4(=2*2), 8(=2*4),6(=2*8),继续循环,2, 4, 8, 6…..

a%10 = 3. (3^n )%10会出现的可能数字:3,9(=3*3),7(=3*9), 1(=3*7),继续循环,3,9,7,1…..

a%10 = 4. (4^n )%10会出现的可能数字:4,6(4*4) , 继续循环,4,6……

a%10 = 5. (5^n )%10会出现的可能数字:5 
a%10 = 6. (6^n )%10会出现的可能数字:6 
a%10 = 7. (7^n )%10会出现的可能数字:7, 9, 3, 1 继续循环, 
a%10 = 8. (8^n )%10会出现的可能数字:8, 4, 2, 6 继续循环, 
a%10 = 9. (9^n )%10会出现的可能数字:9, 1 继续循环,

重点是继续循环,发现,最大情况下,都是以4位数字循环,换句话说,(a^n)%10 和(a^(n+4))%10是相等的,当然,这里n不等于0. 如果这里看懂了,那这里差不多就ok了。

我们把n –> (n%4)+4. 这里加4的原因是为了防止n%4==0. ,并且我们没有考虑n==0的情况。这个单独处理一下。

OK 
如果n != 0. 
(a^n)%10 = (a^(n%4+4))%10 = ((a%10)^(n%4+4)) % 10

另外友情提示:对于n,如果是字符串存储,(十进制) 我们只需要取最后的2位数,用它代替n即可,因为999, 900肯定是可以被4整除的,我们只需要99即可,用99%4+4。 
如果是int 型或longlong。(二进制) (n & 11)按位“与”可以取出后2位,代替n%4,(注意不能代替n%4+4 . 加4一直需要),第3位是4,肯定可以被4整除。

第二种,快速幂取模: 
从一般取模的代码中,我们可以发现,每次都是重复的乘a。那么能否考虑,翻倍呢。看下面代码, 
这部分代码来源于 
http://www.cnblogs.com/yinger/archive/2011/06/08/2075043.html 
简单分析一下,类似于折半查找。

(a^n) %b

long exp_mod(long a,long n,long b)
{
    long t;
    if(n==0) return 1%b;
    if(n==1) return a%b;
    t=exp_mod(a,n/2,b);
    t=t*t%b;
    if((n&1)==1) t=t*a%b;
    return t;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这个代码就是一般的折半,递归,折半….。注意,(n&1) 按位“与 ”,如果是奇数,那么结果是1,否则结果是0.

来个惊喜一点的代码: 
来源于大神的回复 
http://www.cnblogs.com/yinger/archive/2011/06/08/2075043.html

(a^n) %b

int exp_mod(int a,int b,int n){
    int r=1;
    while(b){
        if(b&1)r=(r*a)%n;
        a=(a*a)%n;
        b>>=1;       // b = b>>1;
    }
    return r;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

主要思想还是折半。但是运用的非常巧妙,假设b是一个偶数,并且b/2.b/4,b/8.。。。。一直到2都是偶数,

OK,假设b == 16. a==2. 
b = 16,8,4,2,1 
我们发现,只有最后一次,b==1,时,我们进入了if语句, 
我们查看while循环里面,a的变化。(这里先不考虑模) 
b=16,进入while a = 2^2; 
b=8, a = 2^4; 
b=4, a = 2^8; 
b=2, a = 2^16 
b=1, ,进入if语句,r = a, 此时返回的是r。

假设b=18. 
b=18,进入while a = 2^2; 
b=9, 此时,进入了if语句。r=2^2. a = 2^4; 
b=4, a = 2^8; 
b=2, a = 2^16 
b=1, ,进入if语句,r = r*a = 2^2 * 2^16 , 此时返回的是r。

我们发现,在b=9时,我们用r保存了2^2. 为什么呢。反过来,从下往上看,我们要计算2^18次方。但是我们此时只知道2^8的结果,我们并不知道2^9是多少。因此,我们用了2个2^8. 此时,我们还是差了一个2^2. 
r保存的刚好是这个值。

当b是奇数时。b越小,说明离18越远,从下往上乘,我们一开始是差一个2(b=8和b=9差一个2),一直往上翻倍,即到了18,差2个2.即2^2。因此,r的值也需要越来越大。 (这里需要好好理解。) 
每碰到一次奇数,我们都会和原来的差一个2. 然后这个奇数距离18的距离越远。我们需要补回去的值就越大,即r的值。

重新举一个例子: 
b=22 进入while a = 2^2 
b=11 进入if,r = 2^2. a=2^4 ——>要补回2^2 
b=5 进入if, r=2^2 * 2^4. a = 2^8 ——> 要补回2^4 
b=2 a=2^16 
b=1,进入if, r = 2^2 * 2^4 * 2^16 
当b=11时,只知道2^5,用了两个,可以变成2^10. 
和11相差一个2.再到22即相差两个2. 
当b=5时,只知道2^2.用了两个。可以变成2^4,相差一个2. 再到10(11)时,翻倍,变成了2个。 
(10到11,在b=11那里已经补回来了),再到22,翻倍,变成2个。

第三种,欧拉函数: 
先介绍一个定义,互质:a和b互质,即a和b除了1以为,没有其他公约数。 
对正整数n,欧拉函数是小于n的数中与n 互质的数的数目, 
φ(8)=4,因为1,3,5,7均和8互质。

费马小定理: 
对于互质的整数a和n,有a^φ(n) ≡ 1 mod n 
即(a^φ(n)) % n = 1 %n = 1;

我们常常会见到。求(a^n)%1000000007, n>1000000007. 
因为m=1000000007是质数,毫无疑问,φ(m) = m-1 = 1000000006.

假设n = k * 1000000006 +r .其中r是余数, 
我们利用费马小定理降级处理。

(a^n)%1000000007 = (a^( k * 1000000006 +r ))%1000000007 
=(a^( k * 1000000006 ))%1000000007 * (a^r )%1000000007 
=1 * (a^r )%1000000007

直接把n变成了r。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值