HDU 1576 -- A/B (總結乘法逆元的幾種求法)
題目鏈接: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
Input 數據的第一行是一個T,表示有T組數據。
每組數據有兩個數n(0 <= n < 9973)和B(1 <= B <= 10^9)。
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去除,余數相同。
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 (總結乘法逆元的幾種求法)