zoukankan      html  css  js  c++  java
  • 基础数论 逆元 欧拉及其扩展 小费马和扩展欧里几德 线性求组合数逆元 求大组合数的模 卢卡斯定理

    逆元

    基本的逆元求法


    求某个数的逆元。

    ​ 对于除法的逆元,乘法的倪源直接求:相乘取模,在处理除法的逆元的时候,我们假定 :

    a/b = x (mod p) (1)

    a*b_1 = x (mod p) (2)

    b*b_1 =1 (mod p)(3)

    我们可以在1式的分子和分母同时乘以b_1,,(1)式可以变成(2)式。那么求解x的问题就变成了求解b_1;

    首先我们用扩展欧里几德的方法来求逆元

    我们对(3)式进行如下变换:

    b* Y = 1 (mod p ) <==> b* Y+ p* X = 1 的一组解

    我们定义第二个等式的右边为 c , c必须满足 是 gcd(a,b)的倍数,因为 b , p 都是gcd(p,b)的倍数,得到一个重要结论,只有两个数互质才有逆元。

    接下来我们证明另一个性质:X,Y的解可有有扩展欧里几德算法来求得。代码6如下:

    //代码段 6 
    #include<iostream>
    #define ll long long
    using namespace std;
    //x的系数是被模的数,y的系数是模数,根据欧里几德的运算,我们可以找一组解
    //每次回溯时,x的值被y取代,y的值为px-a/b*y,讲道理,a多数大于b
    //x小于y
    ll x,y;
    ll exgcd(ll a,ll b){
    	if(b==0){
    		x=1;y=0;//创造一组解;
    		return a; 
    	}
    	else{
    		exgcd(b,a%b);
    		ll tx=x;
    		x=y;
    		y=tx-a/b*y;
    	}
    }
    int main() {
    	ll a,b,t;
    	cin>>a>>b;
    	t=exgcd(a,b);
    	if(t%1!=0)	return 0;
    	cout<<(x%b+b)%b<<endl; 
    	return 0;
    }
    

    根据上述的一个重要结论,存在逆元的条件是 两个数互质,我们根据这一性质可以引入费马定理:

    a ^(p-1) = 1 (mod p ) if( gcd(a, p) == 1 )

    ==> a_1 = a^(p-2) //快速幂做法

    代码间上文代码段3,详见下文

    小费马定理扩展: 欧拉定理

    1.a^f(p) = 1 (mod p ) if ( gcd(a,p)==1 ) ==> a^b = a^(b % f(p) ) mod( p )

    我们先把大部分次幂用来凑1 , 即是:

    **af(p)*x * ar == ar (mod p)

    2.ab = ab ( gcd(b,p)!=1 , b< f(p)) (mod p)

    3. ab = ar * af(p) (mod p ) if( gcd(a,p)!=1, b>=f(p) )

    蓝桥杯 -子集选取 一题中就设计的对幂指数去模;


    我们来复习一下欧拉函数,和唯一分解定理

    唯一分解定理

    任何一个数都可以分成若干个质数的幂次积如下:

    **K= a1p1 * a2p2 * a3p3 * ......* anpn **

    我们可以通过分解,我们直接贴代码段7如下

    #include<iostream>
    #include<vector>
    using namespace std;
    vector< pair<int ,int> > vt;//可以转成两个数组来记录
    int main(){
        int n;
        cin>>n;
        int cnt=2,ti=0;
        while(n){
            while(n%cnt==0){
                ti++;
                n/=cnt;
            }
            if(ti!=0){
                vt.push_back(make_pair<cnt,ti>);
                ti=0;
            }
            cnt++;
        }
    }
    

    **但是如果n 含有一个特别大的质数p,那么cnt 需要增加到p ,循环结束,显然这样做的代价太大了,我们可以通过分析将复杂度将至根号级别,证明如下:

    如果p = p1* p2 其中必有一个小于等于sqrt(q) 若cnt*cnt>p则 p 必然是个素数;

    修改代码段7.1如下:

    int cnt=2,ti=0;
    while(n){
        while(n%cnt==0){
            n/=cnt;
            ti++;
        }
        if(ti!=0)	vt.push_back(make_pair<cnt,ti>),ti=0;
        cnt++;
        if(cnt*cnt>n)	break;
    }
    if(n!=1)	vt.push_back(make_pair<n,1>);//最后一个数也是他的因子
    

    如果那当数字非常多的时候可以进行常数优化,我们可以先用线性筛把素数筛出来,然后直接来除以素数; 修改代码段7.2如下:

    int ss[100000],n,cnt=0;
    bool vis[100000]={0};
    void pre(int k){
        vis[1]=true;
        for(int i=2;i<=k;i++){
            if(!vis[i])	ss[cnt++]=i;
            for(int j=0;ss[j]*i<=k&&j<cnt;j++){
                vis[ss[j]*i]=1;
                if(i%ss[j]==0) 	break;
            }
        }
    }
    
    int p1=0,ti=0;
    while(n){
        while(n%ss[p1]==0){
            ti++;
            n/=ss[p1];
        }
        if(ti!=0)	vt.push_back(make_pair<ss[p1],ti>),ti=0;
        p1++;
        if(ss[p1]*ss[p1]>n)	break;
    }
    if(n!=1)	vt.push_back(make_pair<n,1>);
    

    欧拉函数

    公式

    **N=p1c1 * p2c2 * p3c3 * .....* pncn **

    方法1:容斥原理

    欧拉函数指的是小于N且与N互质的数,互质就是没有公因子,我们把N个数中是p1,p2,....,pn的倍数的数全部删掉就是N的欧拉函数,即: N- N/pi+N/pipj-N/pipjpk... 经过整理的到

    N =N*(1-1/p1)* (1-1/p2)* (1-1/p3)* ... * (1-1/pn)

    证法二: 根据质数的欧拉函数

    f(N)=N-1 (if N 为质数)

    f(N)=f(p1k1)*....* f(pnkn)

    其中f(p1k1 ) = pk1 -pk1-1=p1k1 *(1-1/p1)

    因为p1的倍数有p1k1-1

    代码段8如下:

    //在代码⑦的基础上修改就可以了
    int sum=n,p1=2;
    while(n){
        if(n%p1==0){
    		sum=sum*(p1-1)/p;
            while(n%p1==0)	n/=p1;
        }
        p1++;
        if(p1*p1>n)	break;
    }
    sum=sum*(n-1)/n;
    
    

    扩展小结

    1. 求更号k的等效数,求x2 = k (mod p ) ,x为k的一个等效解,只能一个个尝试。
    2. 唯一分解定理的扩展,一个数的因子个数和因子和

    唯一分解定理的扩展:所有因子和与因子个数

    根据唯一分解式 :

    **N=p1c1 * p2c2 * p3c3 * .....* pncn **

    对于每个质数我们可以有0,pi, pi2 , pi3 , ... , pici 中选择方法。所以容易得到个数函数:

    S(N)=(1+c1)*(1+c2)*...*(1+cn)

    他们的和我们可以根据乘法原理来设计:就是n个位置上的数的乘积,而且每个位置的的数是多变的;

    可以推导出这个乘法式子

    T(N)=(P10 + P11 + P12 + ... + P1c1 )(P20 + P21 + P22 + P23 + ... + P2c2).....(Pn0 + Pn1 + +Pn2 + ... + Pncn )

    根据等比数列求和公式

    Pi0 + pi1 +....+ pic1 = (pici+1 -1)/(pi-1)

    代码段9 如下:

    #include<iostream>
    #define  ll long long 
    using namespace std;
    
    ll dcpsum(ll x){
        ll ans=1;
        ll cnt=1,tem;
        while(cnt<=n){
            if(n%cnt==0){
                tem=1;
                while(n%cnt==0){
                    tem*=cnt;
                    n/=cnt;
                }
                ans*=(tem*cnt-1)/(cnt-1);
            }
            cnt++;
            if(cnt*cnt>n)	break;
        }
        if(n!=1)	ans*=(n+1);
        return ans;
    }
    






    线性求逆元


    适用于小于一个较大的质数的一堆数的逆元求解。

    证明

    p=ki+r(r<i)
    k
    i+r=0(mod p)
    kr_1+i_1=0(mod p)
    i_1=-k
    r_1(k=p/i);

    这样可以由较小的推较大的逆元。
    i_1正整数化,可以选择i_1=(p-p/i)r_1%p;或者i_1=-(p/i)r_1%p+p%p

    代码1如下:

    //代码段1
    #include<iostream>
    using namespace std;
    int main(){
        int p=1e9+9;
        long long inv[1000000009];
        inv[0]=0,inv[1]=1;
        for(int i=2;i<p;i++){
            inv[i]=(p-p/i)*in[p%i]%p;
        }
    }
    

    求组合数的逆元

    组合数C(n,m),可以展开成阶乘的形式: n!/(n-m)!/m!, 可以看成n!乘以inv((n-m)!)*inv(m!),对于所有组合数我们只要预处理出来阶乘的逆元就可以了,对于阶乘的逆元因为存在1/(n+1)!*(n+1)=1/n!,所以inv(n!)*inv(n+1)=inv((n+1)!)

    代码2:

    //代码段 2
    #include<iostream>
    #define ll long long
    using namespace std;
    ll ha=1r9+9;
    const int N=1e9;
    ll fac[N],inv[N];//阶乘的逆元和阶乘倒数的逆元
    ll C(int n,int m){
    	return fac[n]*inv[n-m]*inv[m]%ha;
    }
    void pre(int n){
        fac[1]=1;for(int i=2;i<=n;i++)	fac[i]=fac[i-1]*i%ha;
        //求阶乘的逆元
        inv[1]=1;for(int i=2;i<=n;i++)	inv[i]=(ha-ha/i)*inv[ha%i]%ha;
        //线性求逆元
        inv[0]=1;for(int i=1;i<=n;i++) 	inv[i]=inv[i-1]*inv[i]%ha;
        //求阶乘的逆元
    }
    
    
     ### 求单个数的逆元(logN)
    

    小费马定理加上快速幂

    证明

    if(gcd(a,b)==1)(b>a)

    ​ a^(b-1)=1 (mod b)

    ​ so: a_1=a^(b-2);

    这就是小费马定理;

    代码3

    //代码段3
    #include<iostream>
    #define ll long long
    using namespace std;
    const int ha=;
    ll pd(ll a,int b){
        ll res=1;
        while(b){
            if(b&1)	res=res*a%ha;
            b>>=1;
            a=a*a%ha;
        }
        return res;
    }
    

    大组合数求模


    利用卢卡斯定理,这里直接介绍结论

    C(n,m)%p=C(n/p,m/p)*C(n%p,m%p)%p;

    这里的n,m可能还是很大,所以可以用递归的迭代形式

    //代码段 5
    #include<iostream>
    #define ll long long 
    using namespace std;
    const int ha=;
    ll pd(ll a,ll b){
    	ll res=1;
        while(b){
            if(b&1)	res=res*a%ha;
            b>>=1;
            a=a*a%ha;
        }
        return res;
    }
    
    ll comp(ll a,ll b){
        if(a<b)	return 0;
        if(a==b)	return 1;
        if(b>a-b) b=a-b;
        ll ans=1,ca=1,cb=1;
        for(int i=0;i<b;i++0{
            ca=ca*(a-i)%ha;
            cb=cb*(b-i)%ha;
        }//求组合数
        ans=ca*pd(cb,ha-2)%ha;//利用小费马求阶乘的逆元,如果p比较小,n,m特别大,可以预处理出组合数的逆元。
        return ans;
    }
    ll lucas(ll a,ll b){
        ll ans=1;
        while(a&&b){
            ans=ans*comp(a%ha,b%ha)%ha;
            a/=m;b/=m;
        }
        return ans;
    }
      //卢卡斯定理的递归形式。
    ll lucas2(ll a,ll b){
    	return b?lucas2(a/ha,b/ha)*comp(a%ha,b%ha)%ha:1;
    }
    

    复杂度分析

    在线性求组合数的逆元的时候是O(ha)+log(ha,m,n);

    总体复杂度O(ha)

  • 相关阅读:
    mysql索引及优化
    mysql5.5 uuid做主键与int做主键的性能实测
    php生成UUID
    Android 图片的裁剪与相机调用
    Android GPS 临近触发
    Android中GPS类及方法简介
    永久删除 tadb.exe
    linux服务器调整参数支持高并发
    隐藏nginx 版本号信息
    nginx 重写 rewrite 基础及实例
  • 原文地址:https://www.cnblogs.com/hjw201983290498/p/13411938.html
Copyright © 2011-2022 走看看