大數取模運算,快速冪取模運算
1.快速冪取模
快速冪取模就是在O(logn)內求出a^n mod b的值。演算法的原理是ab mod c=(a mod c)(b mod c)mod c
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;
}
2.大數取模運算Barrett reduction
Barrett reduction演算法的證明和使用。
作者剛做完了課程設計作業,閒來無事寫篇文章。
大數中的指數運算,需要對一個數進行取模,因為最大可能二進位制下2048位的2048位次方,所以必須邊做乘法邊取模。
乘法使用快速冪,如果底數位數是x,指數位數是e,底數足夠大的話,複雜度取決於模數,模數是m位的話,複雜度是O(m*m*e)。程式裡,大數的儲存是2的32次方進位制的,這裡的m*m要除以(32*32)。
取模運算,如果直接呼叫大數取模,因為除法是很慢的,所以效率很低。用Barrett reduction演算法可以將除法轉化為乘法和位運算,減法。
因為模數是任意的,所以不用蒙哥馬利演算法。
作業裡只要求完成非負的大數運算,後面證明中預設大數都是非負的。
下面介紹Barrett reduction演算法
求 x mod m
m的位數是k
使用條件: x位數不大於2*k。對同一個數反覆取模。
使用方法(這裡是非負數):
計算常量 mu=b^2k / m
取模時:
q1 = x / b^(k-1)
q2 = q1 * mu
q3 = q2 / b^(k+1)
r1 = x % b^(k+1)
r2 = (q3 * m) % b^(k+1)
r = r1 - r2
if ( r > m ) r -= m
return r
很多資料把mu=b^2k / m寫成了mu=b^k / m,作者被坑了,很生氣,決定放假寫這麼一個文章。另外,百度上基本看不到什麼相關的證明。
證明:
[]表示取整,b是大數的進位制
只需要一次除法預處理。
之後的除法和取模都是對進位制b的若干次方進行的,所以位運算即可。
乘法時,因為最後結果是要取最後k+1位的,所以在乘的時候,可以直接把k+1位前面的丟棄,減少迴圈次數。作者是另外寫了一個限制位數的乘法函式,效率提升了一些。
證明部分截圖自word實驗報告。
大數取模:一般取模+技巧取模+快速冪取模+尤拉函式(費馬小定理)
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;
}
第一種,技巧取模:
(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^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;
}
這個程式碼就是一般的折半,遞迴,折半….。注意,(n&1) 按位“與 ”,如果是奇數,那麼結果是1,否則結果是0.
(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;
}
主要思想還是折半。但是運用的非常巧妙,假設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。