整理自:https://www.cnblogs.com/linyujun/category/784324.html

首先,引入同余定理:

(a +  b) % p = (a%p +  b%p) %p  (对)

(a  -  b) % p = (a%p  -  b%p) %p  (对)

(a  *  b) % p = (a%p *  b%p) %p  (对)

(a  /  b) % p = (a%p  /  b%p) %p  (错)

为什么除法是错的呢,举一个例子就知道了:(100/50)%20 = 2       ≠      (100%20) / (50%20) %20 = 0

但是在有些题目中我们又不得不在除法的时候求余,这时候就要引入一个新的概念了,数论倒数,又称逆元。

逆元:

我们知道,如果,a*x = 1,那么x是a的倒数,x = 1/a,那数论中,大部分情况都有求余,所以现在问题变了

a*x  = 1 (mod p),那么x一定等于1/a吗,不一定,

所以这时候,我们就把x看成a的倒数,只不过加了一个求余条件,所以x叫做    a关于p的逆元

比如2 * 3 % 5 = 1,那么3就是2关于5的逆元,或者说2和3关于5互为逆元

这里3的效果是不是跟1/2的效果一样,所以才叫数论倒数

a的逆元,我们用inv(a)来表示(a和p互质,a才有关于p的逆元

 

逆元怎么求:

一:费马小定理

                                          费马小定理          a^(p-1) ≡1 (mod p)

a^(p-1) ≡1 (mod p)

两边同除以a

a^(p-2) ≡1/a (mod p)

什么,这可是数论,还敢写1/a

应该写a^(p-2) ≡ inv(a) (mod p)

所以inv(a) = a^(p-2) (mod p)

这个用快速幂求一下,复杂度O(logn)

费马小定理中p要求是质数,如果p不是素数,就不能用费马小定理求了

求出来的逆元不一定是最小的

#include <cstdio>
typedef long long LL;
LL pow_mod(LL a, LL b, LL p) //a的b次方求余p
{
    LL ret = 1;
    while(b)
    {
        if(b & 1) ret = (ret * a) % p;
        a = (a * a) % p;
        b >>= 1;
    }
    return ret;
}
LL Fermat(LL a, LL p) //费马求a关于b的逆元
{
    return pow_mod(a, p-2, p);
}
int main()
{
    LL a, p;
   scanf("%lld%lld", &a, &p);

        printf("%lld\n", Fermat(a, p));

}

 

二:扩展欧几里德算法

a*x + b*y = 1

如果ab互质,有解

这个解的x就是a关于b的逆元

y就是b关于a的逆元

为什么呢?

你看,两边同时求余b

a*x % b + b*y % b = 1 % b

a*x % b = 1 % b

a*x = 1 (mod b)

你看你看,出现了!!!(/≥▽≤/)

所以x是a关于b的逆元

反之可证明y

#include<cstdio>
typedef long long LL;
void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d)
{
    if (!b)
    {
        d = a, x = 1, y = 0;
    }
    else
    {
        ex_gcd(b, a % b, y, x, d);
        y -= x * (a / b);
    }
}
LL inv(LL t, LL p) //如果不存在,返回-1
{
    LL d, x, y;
    ex_gcd(t, p, x, y, d);
    return d == 1 ? (x % p + p) % p : -1;
}
int main()
{
    LL a, p;
    while(~scanf("%lld%lld", &a, &p))
    {
        printf("%lld\n", inv(a, p));
    }
}

中国剩余定理:

能求解什么问题呢?

问题:

一堆物品

3个3个分剩2个

5个5个分剩3个

7个7个分剩2个

问这个物品最少有多少个?

模板:

#include<stdio.h>
#include <iostream>
using namespace std;
//扩展欧几里得算法
int exgcd(int a,int b,int &x,int &y)
{
    int d;
    if(b==0)
    {
        x=1;y=0;
        return a;
    }
    d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
 //中国剩余定理 ,r[]存放余数 ,prime[]存放两两互质的数
int Chinese_Remainder(int r[],int prime[],int len)
{
    int i,d,x,y,m,n=1,sum=0;
    //计算所以除数的积n,也是所以除数的最小公倍数
    for(i=0;i<len;i++)
        n*=prime[i];
    //计算符合所以条件的数
    for(i=0;i<len;i++)
    {
        m=n/prime[i];//计算除去本身的所有除数的积m
        d=exgcd(prime[i],m,x,y);//计算w[i]*x+m*y=gcd(w[i],m)的一个解y
        //累加整数解y的同并不断对n取余,其利用公式:(a+b)%c=(a%c+b%c)%c
        sum=(sum+y*m*r[i])%n;
    }
    return (n+sum%n)%n;//满足所以方程的最小解
}
int main()
{
    int n,i;
    int prime[15],r[15];
    scanf("%d",&n);

        for (i=0;i<n;i++)
        {
            scanf("%d%d",&prime[i],&r[i]);
        }
        //printf("%d\n",Chinese_Remainder(b,w,n));
        printf("%d\n",Chinese_Remainder(r,prime,n));

    return 0;
}

比如poj 2891  http://poj.org/problem?id=2891

【题目大意】

给出k个模方程组:x mod ai = ri。求x的最小正值。如果不存在这样的x,那么输出-1.

【题目分析】

由于这道题目里面的ai、ri之间不满足两两互质的性质,所以不能用中国剩余定理直接求解。

AC代码如下:

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PLL;
LL a[100000], b[100000], m[100000];
LL gcd(LL a, LL b){
    return b ? gcd(b, a%b) : a;
}
void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){
    if (!b) {d = a, x = 1, y = 0;}
    else{
        ex_gcd(b, a % b, y, x, d);
        y -= x * (a / b);
    }
}
LL inv(LL t, LL p){//如果不存在,返回-1 
    LL d, x, y;
    ex_gcd(t, p, x, y, d);
    return d == 1 ? (x % p + p) % p : -1;
}
PLL linear(LL A[], LL B[], LL M[], int n) {//求解A[i]x = B[i] (mod M[i]),总共n个线性方程组 
    LL x = 0, m = 1;
    for(int i = 0; i < n; i ++) {
        LL a = A[i] * m, b = B[i] - A[i]*x, d = gcd(M[i], a);
        if(b % d != 0)  return PLL(0, -1);//答案,不存在,返回-1 
        LL t = b/d * inv(a/d, M[i]/d)%(M[i]/d);
        x = x + m*t;
        m *= M[i]/d;
    }
    x = (x % m + m ) % m;
    return PLL(x, m);//返回的x就是答案,m是最后的lcm值 
}
int main(){
    int n;
    while(scanf("%d", &n) != EOF){
        for(int i = 0; i < n; i ++){
            a[i] = 1;
            scanf("%d%d", &m[i], &b[i]);
        }
        PLL ans = linear(a, b, m, n);
        if(ans.second == -1) printf("-1\n");
        else printf("%I64d\n", ans.first);
    }
}

 

下面是两个求乘法逆元的模板      转载自:https://blog.csdn.net/dreamdraw_pan/article/details/52959669

辗转相除法求逆元(求a对于mod的逆元, 要求a与mod互素)

ll exgcd(ll a, ll b, ll &x, ll &y)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    ll r = exgcd(b, a % b, x, y);
    ll t = x % mod;
    x = y % mod;
    y = ((t - a / b * y) % mod + mod) % mod;
    return r;
}

求2对于1e9+7的逆元就是 exgcd(2, 1e9+7, x, y),其中x的值就是inv2,

费马小定理求逆元(求a对于mod的逆元,要求mod为素数)

ll power_mod(ll a, ll b, ll mod)
{
    ll ans = 1;
    while (b)
    {
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
inv2 = power_mod(a, mod - 2, mod);

扩展欧几里得:

首先, ax+by = gcd(a, b) 这个公式肯定有解

所以 ax+by = gcd(a, b) * k 也肯定有解

所以,这个公式我们写作ax+by = d,(gcd(a, b) | d)

gcd(a, b) | d,表示d能整除gcd,这个符号在数学上经常见

那么已知 a,b 求 一组解 x,y 满足 ax+by = gcd(a, b) 这个公式

#include<cstdio>
typedef long long LL;
void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d)
{
    if (!b)
    {
        d = a, x = 1, y = 0;
    }
    else
    {
        ex_gcd(b, a % b, y, x, d);
        y -= x * (a / b);
    }
}
int main()
{
    LL a, b, d, x, y;
    while(~scanf("%lld%lld", &a, &b))
    {
        ex_gcd(a, b, x, y, d);
        printf("%lld*a + %lld*b = %lld\n", x, y, d);
    }
}