1. 程式人生 > >HDU 1576 -- A/B (總結乘法逆元的幾種求法)

HDU 1576 -- A/B (總結乘法逆元的幾種求法)

推廣 ont show 乘法逆元 ostream space 同余 乘法 個數

題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=1576

A/B

Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7264 Accepted Submission(s): 5774


Problem Description 要求(A/B)%9973,但由於A很大,我們只給出n(n=A%9973)(我們給定的A必能被B整除,且gcd(B,9973) = 1)。

Input 數據的第一行是一個T,表示有T組數據。
每組數據有兩個數n(0 <= n < 9973)和B(1 <= B <= 10^9)。

Output 對應每組數據輸出(A/B)%9973。

Sample Input 2 1000 53 87 123456789

Sample Output 7922 6060

題意:給出A%9973和B的值,求(A/B)%9973。 解析:(A / B) % p = (A * inv(B) ) % p = (A % p * inv(B) % p) % p,其中p為模數9973, inv(B)為B關於模p的逆元。 同余式: 如果兩個正整數a和b之差能被n整除,那麽我們就說a和b對模n同余,記作:a≡b (mod n)。
a≡b(mod n)等價於a與b分別用n去除,余數相同。
乘法逆元: 如果ax≡1 (mod p),且gcd(a,p)=1a與p互質,此為一個數有逆元的充要條件,此時逆元唯一存在),則稱a關於模p的乘法逆元為x。 逆元的含義:模n意義下,1個數a如果有逆元x,那麽除以a相當於乘以x。 求解逆元的方法: 1. 擴展歐幾裏得算法: 已知整數a、b,擴展歐幾裏得算法可以在求得a、b的最大公約數的同時,能找到整數x、y(其中一個很可能是負數),使它們滿足貝祖等式ax + by = gcd(a, b)。 當a關於模b的逆元存在,有gcd(a, b) == 1,即擴展歐幾裏得算法可求得x, y滿足ax + by = 1, 兩邊同時余b, ax % b + by % b = 1 % b ax % b = 1 % b ax ≡ 1(mod b) 所以x是a的模b乘法逆元,同理y是b的模a乘法逆元
。 算法時間復雜度:O(logn) 模板代碼: 技術分享圖片
 1 int ex_gcd(int a, int b, int &x, int &y) {  // 函數返回gcd(a, b)
 2     if (b == 0) {
 3         x = 1, y = 0;
 4         return a;
 5     }
 6     int r = ex_gcd(b, a % b, y, x);
 7     y -= (a / b) * x;
 8     return r;
 9 }
10 
11 int main() {
12     int a, b, x, y;
13     cin >> a >> b;  // 求a關於模b的逆元
14     cout << (ex_gcd(a, b, x, y) == 1 ? (x % b + b) % b : -1) << endl;  // -1表示逆元不存在
15 
16     return 0;
17 }
View Code

2. 費馬小定理:

內容:假如a是一個整數,p是一個質數,那麽ap - a是p的倍數,可以表示為

ap ≡ a (mod p)

如果a不是p的倍數(即gcd(a, p) == 1),這個定理也可以寫成

ap-1 ≡ 1 (mod p)

變形得a * ap-2 ≡ 1 (mod p),所以a關於模p的逆元x = ap-2 (mod p),用快速冪模可快速求之。

算法時間復雜度:O(logn)

模板代碼:

技術分享圖片
 1 LL pow_mod(LL a, LL b, LL p) {    //a的b次方取模p 
 2     LL ret = 1;
 3     while (b) {
 4         if(b & 1) ret = (ret * a) % p;
 5         a = (a * a) % p;
 6         b >>= 1;
 7     }
 8     return ret;
 9 }
10 LL Fermat(LL a, LL p) {   //費馬小定理求a關於b的逆元 
11         return pow_mod(a, p-2, p);
12 }
View Code

3. 歐拉定理:

歐拉函數:對正整數n,歐拉函數是小於n的正整數中與n互質的數的數目(φ(n) = n(1 – 1/p1)(1 – 1/p2)…(1 – 1/pm), pn為n的所有質因數, φ(1) = 1)。

歐拉定理:若gcd(a, p) = 1,則a^φ(p) ≡ 1 (mod p)。

即 a*a^(φ(p)?1) ≡ 1(mod p),那麽a關於模p的逆元x = a^(φ(p)?1) (mod p)

(當p為素數的時候φ(p)=p-1,則φ(p)-1=p-2可以看出歐拉定理是費馬小定理的推廣)

費馬小定理要求模數p為素數,歐拉定理則沒有此要求,但是似乎還有個問題?如何判斷a是否有逆元呢?

再求一次gcd判斷是否互質嗎?這還不如直接用擴展歐幾裏得算法呢。

可以由逆元性質直接檢驗是否為逆元,看求出的值x與a相乘模p是否為1即可。

算法時間復雜度:O(logn)

模板代碼:

技術分享圖片
 1 #include <iostream>
 2 using namespace std;
 3 typedef long long LL;
 4 LL euler(LL n) {  // 歐拉函數
 5     LL res = n, i;
 6     for (i = 2; i * i <= n; i++) {
 7         if (n % i == 0) {
 8             res = res / i * (i - 1);
 9             while (n % i == 0) n /= i;
10         }
11     }
12     if (n != 1) res = res / n * (n - 1);
13     return res;
14 }
15 LL pow_mod(LL a, LL b, LL p) {  // 快速冪模
16     LL ret = 1;
17     while (b) {
18         if(b & 1) ret = (ret * a) % p;
19         a = (a * a) % p;
20         b >>= 1;
21     }
22     return ret;
23 }
24 
25 int main() {
26     LL a, p, inv;
27     cin >> a >> p;
28     inv = pow_mod(a, euler(p) - 1, p); // inv為a關於模p的逆元
29     if (a * inv % p == 1) cout << inv << endl;  // 由逆元性質檢驗逆元是否存在
30     else cout << -1 << endl;  // 不存在輸出-1
31 
32     return 0;
33 }
View Code

4. O(n)求1~n逆元表

在模質數p下,求1~n逆元(n < p)

inv(a) = (p - p / a) * inv(p % a) % p

證明:

設x = p % a,y = p / a
於是有 x + y * a = p
(x + y * a) % p = 0
移項得 x % p = (-y) * a % p
x * inv(a) % p = (-y) % p
inv(a) = (p - y) * inv(x) % p
於是 inv(a) = (p - p / a) * inv(p % a) % p

模板代碼:

技術分享圖片
 1 #include<cstdio>
 2 const int N = 200000 + 5;
 3 const int MOD = (int)1e9 + 7;
 4 int inv[N];
 5 int init(){
 6     inv[1] = 1;
 7     for(int i = 2; i < N; i ++){
 8         inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
 9     }
10 }
11 int main(){
12     init();
13 }
View Code

總結:

1. 逆元求解一般利用擴歐。

2. 當模數p為質數的時候直接使用費馬小定理。

3. p非質數使用歐拉函數(一般不用)。

HDU 1576 -- A/B AC代碼:

技術分享圖片
 1 #include <iostream>
 2 #define MOD 9973
 3 using namespace std;
 4 typedef long long LL;
 5 LL qmod(LL a, LL b) {   // 快速冪模
 6     LL res = 1;
 7     while (b) {
 8         if (b & 1) res = res * a % MOD;
 9         a = a * a % MOD;
10         b >>= 1;
11     }
12     return res;
13 }
14 
15 int main() {
16     int t; cin >> t;
17     while (t--) {
18         int n, b; cin >> n >> b;
19         cout << 1ll * n * qmod(1ll * b, MOD - 2) % MOD << endl;  // 費馬小定理求b的逆元
20     }
21     return 0;
22 }
View Code

進階題:HDU 5976 -- Detachment (貪心+逆元表+前綴和、積):

技術分享圖片
 1 #include <stdio.h>
 2 const int maxn = 1e5 + 10;
 3 const int MOD = 1e9 + 7;
 4 typedef long long LL;
 5 LL mul[maxn], sum[maxn], inv[maxn];
 6 void init() {
 7     mul[1] = 1;
 8     sum[1] = 0;
 9     inv[1] = 1;
10     for (int i = 2; i < maxn; i++) {
11         sum[i] = sum[i-1] + i;
12         mul[i] = (i * mul[i-1]) % MOD;
13         inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
14     }
15 }
16 
17 int main() {
18     int t, x;
19     init();
20     scanf("%d", &t);
21     while (t--) {
22         scanf("%d", &x);
23         if (x == 1) { puts("1"); continue; }
24         int l = 2, r = maxn, mid, idx;
25         while (l <= r) {
26             mid = (l + r) / 2;
27             if (sum[mid] <= x) idx = mid, l = mid + 1;
28             else r = mid - 1;
29         }
30         int rest = x - sum[idx];
31         LL ans = 0;
32         if (rest == idx) ans = (mul[idx] * inv[2] % MOD * (idx + 2)) % MOD;
33         else ans = mul[idx+1] * inv[idx+1-rest] % MOD;
34         printf("%I64d\n", ans);
35     }
36     return 0;
37 }
View Code

HDU 1576 -- A/B (總結乘法逆元的幾種求法)