HDU 1576 -- A/B (总结乘法逆元的几种求法)

Posted kangkang-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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


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是一个质数,那么a- 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 (总结乘法逆元的几种求法)的主要内容,如果未能解决你的问题,请参考以下文章

hdu1576-A/B-(同余定理+乘法逆元+费马小定理+快速幂)

hdu1576(扩展gcd求乘法逆元)

乘法逆元

HDU 1576 A/B(扩展欧几里得 求 逆元)

(数论)简单总结求逆元的几种方法

HDU-1576 A/B (扩展欧几里得求逆元)