同余&逆元简单总结

同余&逆元


1. 同余

1. 同余的基本概念及性质

  1. x % m = a 即m是 x-a 的一个因子, 则称x与a关于m同余,记作:
    x a ( m o d m )
  2. 同余基本性质:

○1. 自反性: a a ( m o d m )

○2. 对称性: a b ( m o d m ) b a ( m o d m )

○3. 传递性: a b ( m o d m ) , b c ( m o d m ) a c ( m o d m )

○4. 同加性: a b ( m o d m ) c d ( m o d m ) a + c b + d ( m o d m )

○5. 同乘性: a b ( m o d m ) c d ( m o d m ) a c b d ( m o d m )

○6. 同幂性: a b ( m o d m ) a n b n ( m o d m ) n是自然数

○7. 若 a b ( m o d m ) , n | m a b ( m o d n )

○8. 若 a c b c ( m o d m ) , ( c , m ) = d a b ( m o d m d )

○9. 若 ( m , n ) = 1 , a b ( m o d m ) , a b ( m o d n ) a b ( m o d m n )

2. 求解线性同余方程 a x c ( m o d b )

可转化为求解方程: a x + b y = c
(同余方程和线性方程的关系很重要,经常用到!!)
1.预处理:

    if(a<0) a=-a,c=-c;
    while(c<0) c+=b;//保证 a,c 为正

2.第一步: 检验是否有解

    int gcd=Gcd(a,b);
    if((c%gcd)!=0) return -1;//若c不是gcd(a,b) 的倍数,则无解
    //可以转化为直线上的整点来理解

3.第二步:求解同余方程: a x g c d ( a , b ) ( m o d b ) a x + b y = g c d ( a , b )
扩展欧几里得算法

inline int ex_gcd(int a,int b)
{
    if(b==0) {x=1;y=0;return a;}
    int gcd=ex_gcd(b,a%b);
    int tmp=x;x=y;
    y=tmp-a/b*y;//扩欧
    return gcd;
}
//最后得到的x即为原同余方程的一个可行解;

扩欧的证明:
当最后 b’==0 时a’==gcd(a,b) 则此时 a x + b y = g c d ( a , b ) x
x = 1 , y = 0 即得最后的一组解。
考虑从后往前推出 a x + b y = c 的解:
设我们前一步求出的解为 x 1 , y 1 , a , b a , b , x , y
那么因为 g c d ( a , b ) = g c d ( b , a % b ) , a % b = a ( a / b ) b 有:

b x 1 + ( a ( a / b ) b ) y 1 = g c d ( a , b )

则:
b x 1 + ( a ( a / b ) b ) y 1 = a x + b y

整理得:
a y 1 + b ( x 1 ( a / b ) y 1 ) = a x + b y

容易看出:
x = y 1 , y = x 1 ( a / b ) y 1

即证。

4.第四步:根据题意得出答案

若要求出最小正整数解:

    while(x<0) x+=b;x%=b;
    b/=gcd;//mod 要变成 mod/gcd ;(mod 即为 b)
    x=x*c/gcd;//同余的同乘性质
    while(x<0) x+=b;x%=b;//最小正整数解

若要求出解的个数(或所有解)

    int tot=gcd(a,b);// 只有gcd(a,b) 个解
    //要求出每个解,只需对其不断加 b/gcd 即可(同时y-=a/gcd)

3.求解单变量模线性方程组(中国剩余定理)

有如下方程:

{ x a 1 ( m o d m 1 ) x a 2 ( m o d m 2 ) x a 3 ( m o d m 3 ) x a n ( m o d m n )

其中( m 1 m 2 m 3 m n 两两互质 )
为了方便表示,将x设为S
(1)设 M = Π i = 1 n m i , 设 M i = M / m i
(2)可知对于每一个 i : ( M i , m i ) = 1
即:
M i x + m i y = 1
那么 x M 1 的逆元,用 t i 表示
两边同时扩大 a i
M i a i t i + m i a i y = a i

y 的取值与求解无关,可将 a i y 视为 y ,则:

M i a i t i + m i y = a i

那么易知 S = M i a i t i
则原同余方程组通解为:

x = a 1 t 1 M 1 + a 2 t 2 M 2 + . . . . + a n t n M n + k M , k Z

为什么把每一个加起来就行了呢?
因为每一个 M i 都含有因子 m j ( j i ) ,对于其他的同余方程不产生影响。
若要求最小正整数解,则对 M 取模即可。

代码如下:

//中国剩余定理求解单变量模线性同余方程组
int CRT(int a[],int m[],int h)
{
    int ans=0;int M=1;
    for(int i=1;i<=h;i++) M*=m[i];//求M
    for(int i=1;i<=h;i++) {
        ll Mi,ti;
        Mi=M/m[i];//求Mi
        ti=Rev(Mi,m[i]);//求Mi的逆元
        ans+=((a[i]*Mi%M)*ti)%M;//累加答案
        if(ans>=M) ans-=M;//取模
    }
    return ans;
}

4.扩展中国剩余定理

这同样是用来解决单变量模线性方程组的,但是能够应用于模数不互质的情况
其实这个和中国剩余定理没有什么关系,CRT是用构造法,而EXCRT则基于扩展欧基里德算法

做法:
还是如下方程:

{ x a 1 ( m o d m 1 ) x a 2 ( m o d m 2 ) x a 3 ( m o d m 3 ) x a n ( m o d m n )

其中 m 1   m 2   m 3   m 4 m n 不一定互质

我们可以发现左边的式子都是相同的,于是有了同余方程合并这种操作
既然是合并,我们只要讨论两个式子的时候的情况
对于:

{ x a 1   ( m o d   m 1 ) x a 2   ( m o d   m 2 )

可以看做是两个方程:
{ x = a 1 + x 1 m 1 x = a 2 + x 2 m 2

合并一下得到:
a 1 + x 1 m 1 = a 2 + x 2 m 2

移项:
x 1 m 1 = a 2 a 1 + x 2 m 2

假定 a 2 a 1 ,设为 c ,再化为同余方程:
m 1 x 1 c   ( m o d   m 2 )

g c d ( m 1 , m 2 ) = d ,该同于方程有解当且仅当 d | c ,所以如果 d 不整除 c 则整个同余方程组无解
反之,由同余的性质得:

m 1 d x 1 c d   ( m o d m 2 d )

d 1 = m 1 d , d 2 = m 2 d , c 2 = c d
由于此时 d 1 , d 2 一定互质,所以 d 1 在模 d 2 的意义下一定有逆元,记为 d 1 1 ,那么可以解出 x 1 (其实就是扩欧)
x 1 = d 1 1 c 2 + d 2 x 2

回代进一开始的方程:
x = c + ( d 1 1 c 2 + d 2 x 2 ) m 1

展开化简得:
x = d 1 1 c 2 m 1 + c + m 1 m 2 d x 2

于是我们可以得到一个新的同余方程:
x d 1 1 c 2 m 1 + c   ( m o d   m 1 m 2 d )

于是就这样一直合并下去,最后的解就直接出来了(注意最后的模数会变成 l c m ( m 1 , m 2 , . . . , m n ) )

中间结果注意防溢出

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ldb;
const int N=1e5+10;
inline ll Mul(ll a,ll b,ll mod){a%=mod;b%=mod;return (a*b-(ll)((ldb)a*b/mod)*mod+mod)%mod;}
int n;
ll gcd(ll a,ll b){return (b? gcd(b,a%b):a);}
inline ll fpow(ll x,ll k,ll mod)
{
    register ll ans=1;
    while(k){
        if(k&1) ans=Mul(ans,x,mod);
        x=Mul(x,x,mod);
        k>>=1;
    }
    return ans;
}
void exgcd(ll a,ll b,ll &x,ll &y){
    if(!b){x=1;y=0;return;}
    exgcd(b,a%b,x,y);
    register ll tmp=x;
    x=y;y=tmp-a/b*y;
    return;
}
inline void EXCRT()
{
    ll p1,b1,p2,b2;scanf("%lld %lld",&p1,&b1);
    for(register int i=2;i<=n;++i) {
        scanf("%lld %lld",&p2,&b2);
        register ll d=gcd(p1,p2);
        if(b2<b1) swap(b1,b2),swap(p1,p2);
        register ll c=b2-b1;
        if(c%d) return void(puts("no solution"));
        register ll d1=p1/d,d2=p2/d;
        register ll lcm=d1*p2;c/=d;
        register ll inv,y;exgcd(d1,d2,inv,y);
        register ll x1=Mul(inv,c,d2);
        b1=(b1+Mul(x1,p1,lcm))%lcm;p1=lcm;
    }
    printf("%lld\n",b1%p1);
    return;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("EXCRT.in","r",stdin);
    freopen("EXCRT.out","w",stdout);
#endif
    scanf("%d",&n);
    EXCRT();
    return 0;
}

5.卢卡斯定理(大组合数取模)

对于组合数取模,如

C n m   m o d   p

其中p是一个质数,有如下定理:

卢卡斯定理:组合数 C n m 在模意义下等价于把n和m看成一个p进制数,对每一位分别求出组合数后乘起来
比如说假设:
n = a 1 p 0 + a 2 p 1 + a 3 p 3 + a k p k , m = b 1 p 0 + b 2 p 1 + b 3 p 3 + b k p k
那么:

C n m m o d p = i = 0 k C a i b i m o d p

显然如果p很大的话没有什么鸟用
但是当p不是特别大的话,我们可以发现通过这个定理我们要求的组合数的n,m都不会超过p,可以使用阶乘来解决,并且这时阶乘一定和p是互质的,一定存在逆元,通过阶乘逆元我们可以直接算出组合数
复杂度是 O ( p   l o g p n ) ,预处理阶乘逆元就直接是 O ( p + l o g p n ) 了,看上去还是很有用的

主要代码:

inline ll C(ll n,ll m,ll p){
    if(m>n) return 0;
    return fac[n]*fpow(fac[m],p-2,p)%p*fpow(fac[n-m],p-2,p)%p;
}
ll Lucas(ll n,ll m,ll p)
{
    if(!m) return 1;
    return C(n%p,m%p,p)*Lucas(n/p,m/p,p)%p;
}

6.扩展卢卡斯定理

还是这个东西:

C n m   m o d   p

但是p不一定是质数
这个其实和卢卡斯定理也没有什么很大的关系
我们先把p给质因数分解:
p = p 1 k 1 p 2 k 2 p 3 k 3 p n k n

可以看出,如果所有的k都是1的话,我们设 C n m = x ,对每一个 p i 分别用卢卡斯定理求出组合数 C i ,那么就变成了求解一系列的同余方程组:
{ x C 1   m o d   p 1 x C 2   m o d   p 2 x C 3   m o d   p 3 x C 4   m o d   p 4 x C 5   m o d   p 5

于是我们可以用中国剩余定理进行合并,求出最后的x,显然在模p意义下最后只会有唯一解

问题在于我们现在的p可能不是一次,而是有k次,要想用CRT来进行合并只能靠求出 C n m   m o d   p i k i
因为要把同余式 x a   ( m o d   p 1 p 2 ) 拆开必须要满足 p 1 p 2 互质,显然两个相同的数不会互质

于是问题转化为快速求出 C n m   m o d   p i k i
为方便,我们设现在考虑的模数是 p k , p 是一个质数
还是考虑用阶乘来解决:

C n m = n ! m ! ( n m ) !

我们仔细观察可以发现 n ! 中含有的 p 这个因子的个数一定会不少于 m ! ( n m ) ! 中p的个数,我们可以很容易得到一个阶乘中含有的p的个数的递推公式:
f ( n ) = f ( n p ) n p

例如我们要求: 9 ! 2 的个数:
9 ! = 1 × 2 × 3 × 4 × 5 × 6 × 7 × 8 × 9 = 1 × 3 × 5 × 7 × 9 × [ 2 × ( 1 × 2 × 3 × 4 ) ]

这就比较直观了,有了这个的话,我们假设求出阶乘中不含 p 的项的积,这样就可以通过逆元来进行组合数计算了,只需要最后把因该有的 p 给再乘上去就行了

于是问题再次变为如何快速求出阶乘

其实方法在上面 9 ! 的变换中就可以发现了,由于我们不管 p 有多少,发现提出来一个 p 之后,右边那部分的阶乘可以递归进行计算,就是 n p ! ,于是关键在于计算左边
由于是模p意义下,在上面的式子中,可以发现左边其实全部都是1,手玩一下其他的发现显然这个东西是以p个一循环的,并且可能会剩下不超过p个数
所以循环部分算出一个然后快速幂 n / p 次,最后还会剩下 n % p 个,直接暴力算这些
所以对于一次的阶乘要算的次数也不会超过 O ( p ) 次,总共有 l o g p n
所以计算一个阶乘的复杂度为 O ( p   l o g p n )
总复杂度也就是把所有模数的复杂度加起来,最高也不超过最大质因子的复杂度
所以我们就解决了这个问题
剩下的就是算出逆元,求出组合数,处理多余的质因子p,然后CRT合并

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
ll n,m,p;
void exgcd(ll a,ll b,ll &x,ll &y){
    if(!b) {x=1;y=0;return;}
    exgcd(b,a%b,x,y);
    register ll tmp=x;
    x=y;y=tmp-a/b*y;
    return;
}
inline ll Mul(ll a,ll b,ll p){a%=p;b%=p;return (a*b-((ll)(long double)a*b/p)*p+p)%p;}
inline ll fpow(ll x,ll k,ll p){
    register ll ans=1;
    while(k){
        if(k&1) ans=ans*x%p;
        x=x*x%p;k>>=1;
    }
    return ans;
}
ll calc(ll n,ll pi,ll pk){
    if(n<=1) return 1;
    register ll ret=1,i;
    ll rsub=calc(n/pi,pi,pk);
    if(n/pk){//如果大于模数
        for(i=2;i<=pk;++i) if(i%pi) ret=ret*i%pk;
        ret=fpow(ret,n/pk,pk)%pk;
    }
    register ll res=n%pk;
    for(i=2;i<=res;++i) if(i%pi) ret=ret*i%pk;//模意义下,直接枚举模了之后的未统计数也可以
    return ret*rsub%pk;
}
inline ll inv(ll a,ll b)
{
    ll x,y;
    exgcd(a,b,x,y);
    x=(x+b)%b;
    return x;
}

inline ll C(ll n,ll m,ll pi,ll pk)
{
    if(m>n) return 0;
    ll facn=calc(n,pi,pk);
    ll facm=calc(m,pi,pk);
    ll facmn=calc(n-m,pi,pk);//求解三个阶乘
    ll num=0;register ll i;
    for(i=n;i;i/=pi) num+=i/pi;
    for(i=m;i;i/=pi) num-=i/pi;
    for(i=n-m;i;i/=pi) num-=i/pi;//把p这个质因子都提出来单独算(其实算阶乘的时候没有处理这些数)
    //阶乘质因子个数递推 f[i]=f[i/p]+i/p;
    return facn*inv(facm,pk)%pk*inv(facmn,pk)%pk*fpow(pi,num,pk)%pk;
}
inline void EXLucas(ll n,ll m,ll p){
    if(n<m) return void(puts("0"));
    if(n==m||m==0) return void(puts("1"));
    ll ans=0;ll x=p;
    for(register int i=2;i<=p;++i)
        if(!(x%i)){
            register ll pk=1ll;
            while(!(x%i)) pk*=i,x/=i;
            register ll res=C(n,m,i,pk);
            ans=ans+res*(p/pk)%p*inv(p/pk,pk)%p;
            if(ans>=p) ans-=p;
        }
    printf("%lld\n",ans);
    return;
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("EXLucas.in","r",stdin);
    freopen("EXLucas.out","w",stdout);
#endif
    scanf("%lld %lld %lld",&n,&m,&p);
    EXLucas(n,m,p);
}

2.逆元

1.定义:在 mod m 的意义下,设b是a的逆元,则: a b 1 ( m o d m )

2.求解方法:

1.线性递推法
逆元的递推公式:

i n v [ i ] = ( P P / i ) i n v [ P % i ] % P

阶乘逆元的递推公式:

i n v [ i ] = i n v [ i + 1 ] ( i + 1 ) % P

上面的 i n v [ i ] 表示 i ! 的逆元

2.扩展欧基里德法:

在上面我们用扩欧求了如下同余方程的解:

a x b   ( m o d   p )

而逆元是求的这个同余方程:

a x 1   ( m o d   p )

所以把b直接设为1然后扩欧解出x的最小正整数解就是a的逆元了

可以发现a,p一定要互质,不然同余方程无解,即a在a和p不互质的情况下是没有逆元的

3.费马小定理

费马小定理:
当a和p互质且p是质数时,有

a p 1 1   ( m o d   p )

相当于是:

a p 2 a 1   ( m o d   p )

所以 a p 2 就是a的逆元了

4.欧拉定理

欧拉定理:
当a和p互质时,有

a φ ( p ) 1   ( m o d   p )

所以 a 的逆元是 a φ ( p ) 1 ,其实费马小定理是欧拉定理在p是质数时的一个特殊情况

补充:
扩展欧拉定理:

a b a b % φ ( p ) + φ ( p )   ( m o d   p )

证明不会

小结:逆元在a和p互质的情况下才有

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/79327045