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
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。
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 }
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 }
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 }
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 void 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 }
1 fac[0]=fac[1]=1; 2 3 for(int i=2;i<=MAXN;i++)fac[i]=fac[i-1]*i%mod; 4 5 inv[MAXN]=qpow(fac[MAXN],mod-2); 6 7 for(int i=MAXN-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
证明:设 x 是 a 关于 p 的逆元,y 是 b 关于 p 的逆元,即 xa mod p = yb mod p = 1,则
xayb mod p = 1
(ab)*(xy) mod p = 1
即 xy 是 ab 关于模 p 的逆元,即 inv(ab) = xy = inv(a)*inv(b)。
总结:
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 }
进阶题: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 }