zoukankan      html  css  js  c++  java
  • 基础数论(逆元,费马小定理,扩展欧几里得,中国剩余定理)(模板)

    整理自: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
    ", 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
    ", 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
    ",Chinese_Remainder(b,w,n));
            printf("%d
    ",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
    ");
            else printf("%I64d
    ", 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
    ", x, y, d);
        }
    }
    
  • 相关阅读:
    PDB文件详解
    C++模板常用功能讲解
    Windows下多线程编程(二)
    关于静态库中使用全局变量可能导致的问题
    js中的函数
    js中字符串的加密base64
    列表推导式
    函数和方法的区别
    xshell连不上虚拟机
    网络编程,并行,并发和协程
  • 原文地址:https://www.cnblogs.com/jk17211764/p/9677398.html
Copyright © 2011-2022 走看看