• 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 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 }
    View Code
    阶乘逆元求法
    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;
    View Code
    inv(x) 是积性函数

    证明:设 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 }
    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
    ", ans);
    35     }
    36     return 0;
    37 }
    View Code
    作者:_kangkang
    本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
  • 相关阅读:
    C# 获取当前路径7种方法
    C# 获取当前路径7种方法
    C#委托的介绍(delegate、Action、Func、predicate)(转)
    C#委托的介绍(delegate、Action、Func、predicate)(转)
    DevExpressGridView自定义行高
    DevExpressGridView自定义行高
    C#IO读写txt文件的两种方法
    C#IO读写txt文件的两种方法
    NoSQL 简介
    NoSQL 简介
  • 原文地址:https://www.cnblogs.com/kangkang-/p/8437080.html
Copyright © 2020-2023  润新知