数论知识及模板整理

目录

一、质数的判定

1. 试除法判定质数
2. 质因数的分解
3. 质数筛选法(埃氏筛法+线性筛)
4. 米勒罗宾素数检测法(快速判断大质数)

二、约数相关

(1)试除法求约数
(2)求约数个数或约数之和
(3)求最大公因数 / 最小公倍数

三、欧几里得算法

(1)扩展欧几里得算法
(2)线性同余方程

四、快速幂

(1)快速幂算法
(2)大数快速幂(降幂公式)
(3)快速幂求逆元(费马小定理)

五、欧拉函数

六、组合数学

七、高斯消元

八、容斥原理

九、中国剩余定理/扩展中国剩余定理

十、其他

1. 威尔逊定理
2. …


注:以下模板题均可在 ACwing 题库中找到


一、质数的判定

1. 试除法判定质数
bool isprime(int x)
{
    if(x<=1)
        return false;
    for(int i=2;i<=x/i;i++)
    {
        if(x%i==0)
            return false;
    }
    return true;
}


2. 质因数的分解
#include
using namespace std;
int main()
{
     int n;
     cin>>n;
     while(n--)
     {
         int x;
         cin>>x;
         for(int i=2;i<=x/i;i++)
         {
             int p=0;
             while(x%i==0)
             {
                 x/=i;p++;
             }
             if(p)
                cout<<i<<' '<<p<<endl;
         }
         if(x>1)
            cout<<x<<" "<<1<<endl;
         cout<<endl;
     }
    return 0;
}


3. 质数筛选法(埃氏筛法+线性筛)
(1)埃氏筛法

基本原理:从小到大将每个质数的倍数筛去
若某个数没有被它前面的数筛掉,那么它一定是质数。
原因:它不是前面的2~p-1中任何一个数的倍数,那么它是质数

时间复杂度:n*log(log(n)),接近线性

const int N = 1e6+5;
bool isprime[N];
int prime[N];
int cnt;
void init(int n)
{
    isprime[1]=true;
    for(int i=2;i<=n;i++)
    {
        if(!isprime[i])
        {
            prime[++cnt]=i;
            for(int j=i+i;j<=n;j+=i)
                isprime[j]=true;
        }
    }
}


(2)线性筛法

基本原理:每个数只会被它最小的质因数筛掉,那么每个数只会被筛一次,所以时间复杂度为o(n)

const int N = 1e6+5;
bool isprime[N];
int prime[N];
int cnt;
void init(int n)
{
    isprime[1]=true;
    for(int i=2;i<=n;i++)
    {
        if(!isprime[i])
           prime[cnt++]=i;
        for(int j=0;prime[j]<=n/i;j++)
        {
            isprime[prime[j]*i]=true;
            if(i%prime[j]==0)
                break;
        }
    }
}


4. 米勒罗宾素数检测法(快速判断大质数)
适用范围:较大数的较快素性判断
原理是费马小定理:如果p是素数,则a ^ (p-1)%p == 1,加上二次探测定理:如果p是一个素数,则x^2%p==1的解为,则x=1或者x=n-1。

一次检测中:

主要是把一个数n的n-1分解成d*2^ r的形式,其中d为奇数,正向过程是a^ n%p如果是1,就继续分解
a^ (n/2)%p,(a为一个与n互素的数)看是否为1,;如果是n-1就停止分解,说明至此无法判断是否为素数;如果不等于这两个值,则一定为合数。而在写代码过程是这个过程的逆向过程,先分解到底,看最后这个a^d%p是否为1或n-1,如果是说明已经分解到底了,也就是通过了此次素性测试。如果不是,说明在正向过程中出现了要么a的某次方为n-1,根据算法停止了检测过程;要么就是中间的某一个结果不等于这两个数,那么就是合数。就从最后往前面推,每一步看满不满足上述条件。直到判断为合数或者终止检测的那一步。

#include
using namespace std;
typedef long long ll;
ll q_mul(ll a,ll b,ll p)
{
	ll ans=0;
	while(b)
	{
		if(b&1)
			ans=(ans+a)%p;
		a=(a<<1)%p;
		b>>=1; 
	}
	return ans;
}
ll q_pow(ll a,ll b,ll p)
{
	ll ans=1;
	while(b)
	{
		if(b&1)
			ans=q_mul(ans,a,p);
		a=q_mul(a,a,p);
		b>>=1;
	}
	return ans;
}
bool Miller_Rabin(ll n){
    if(n==2)return true;
    if(n<2||!(n&1))return false;
    int t=2,r=0;
    ll m=n-1;
    while(m%2==0){
        r++;
        m>>=1;
    }
    srand(100);
    while(t--)
	{
        ll a=rand()%(n-1)+1;
        ll x=q_pow(a,m,n),tmp=0;
        for(int i=0;i<r;i++){
            tmp=q_mul(x,x,n);
            if(tmp==1&&x!=1&&x!=n-1)return false;
            x=tmp;
        }
        if(tmp!=1)return false;
    }
    return true;
}

部分借鉴自 大神博客



二、约数相关

1. 试除法求约数
void get_ans(int n)
{
    vector<int> ans;
    for(int i=1;i<=n/i;i++)
    {
        if(n%i==0)
        {
            ans.push_back(i);
            if(i!=n/i)
                ans.push_back(n/i);
        }
    }
    sort(ans.begin(),ans.end());
    for(auto x:ans) cout<<x<<" ";
    cout<<endl;
}


2. 求约数个数或约数之和

原理:唯一分解定理
任意正整数 n = (a1 ^ p1) * (a2 ^ p2) * (a3 ^ p3) … * (ak ^ pk)
其中a1…ak均为质数
那么约数之和 sum=(p1+1) * (p2+1) * (p3+1) … (pk+1)
即对第一个质因子可以有p1+1种选法,一直到对第k个质因子,有pk+1种选法,把选中的数乘起来就是总约数个数

#include
#include
using namespace std;
typedef long long ll;
const int mod =1e9+7; 
int main()
{
    int n;
    cin>>n;
    unordered_map<int,int> mp;
    while(n--)
    {
        int x;
        cin>>x;
        for(int i=2;i<=x/i;i++)
            while(x%i==0)
            {
                x/=i;
                mp[i]++;
            }
        if(x>1)
            mp[x]++;
    }
    ll ans=1,ans2=1; //分别为约数个数,约数之和
    for(auto x: mp)
    {
    	int t=x.first,tt=x.second;
        ll res=1;
        while(tt--) res=(res*t+1)%mod;
        ans=ans*res%mod;
        ans2=ans2*(x.second+1)%mod;
    }
    cout<<ans<<" "<<ans2<<endl;
    return 0;
}


3. 求最大公因数 / 最小公倍数
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}
int lcm(int a,int b)
{
	return a/gcd(a,b)*b;
}


三、欧几里得算法

1. 扩展欧几里得算法
(1)求ax+by=gcd(a,b)的任意一组解:

因为gcd(a,b)=gcd(b,a%b)

为计算方便 , 递归的时候交换x,y位置,那么原式就变为

by+(a%b)x=gcd(a,b)

=>by + ( a - a / b (下取整) * b) * x = gcd(a,b)

=>ax + b(y - a / b * x) = gcd(a,b)

#include
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
    if(!b)
    {
        x=1;y=0;
        return a;
    }
    int r=exgcd(b,a%b,y,x);
    y-=a/b*x;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int x,y,a,b;
        scanf("%d%d",&a,&b);
        int r=exgcd(a,b,x,y);
        printf("%d %d\n",x,y);
    }
    return 0;
}


(2)求ax+by=c的解

若c%gcd(a,b)!=0那么无解

根据上面求出一组x0, y0满足a * x0 + b * y0 = gcd(a,b)

令d=c/gcd(a,b), t=c/d

ax0+by0=d

ab/d+ax0-ab/d+by0=d

a(x0+b/d)-b(y0-a/d)=d

两边同时乘以t, 即解得 x=(x0+b/d)*t, y=(y0-a/d)*t

#include
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
    if(!b)
    {
        x=1;y=0;
        return a;
    }
    int r=exgcd(b,a%b,y,x);
    y-=a/b*x;
}
int main()
{
	int a,b,c,x0,y0,x,y;
	scanf("%d%d%d",&a,&b,&c);
	int d=exgcd(a,b,x0,y0);
	if(c%d!=0)
		printf("no solution\n");
	else
	{
		int t=c/d;
		x=(x0+b/d)*t;
		y=(y0-a/d)*t;
		printf("x=%d y=%d\n",x,y);
	}
	return 0;
}


2. 线性同余方程

题目链接

给定n组数据ai,bi,mi,对于每组数求出一个xi,使其满足ai∗xi≡bi(mod mi),如果无解则输出impossible。

输入格式
第一行包含整数n。

接下来n行,每行包含一组数据ai,bi,mi。

输出格式
输出共n行,每组数据输出一个整数表示一个满足条件的xi,如果无解则输出impossible。

每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。

输出答案必须在int范围之内。

数据范围
1≤n≤105,
1≤ai,bi,mi≤2∗109
输入样例:
2
2 3 6
4 3 5
输出样例:
impossible
7

#include
using namespace std;
typedef long long ll;
int exgcd(int a,int b,int &x,int &y)
{
    if(!b)
    {
        x=1;y=0;
        return a;
    }
    int r=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return r;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int a,b,m,x,y;
        scanf("%d%d%d",&a,&b,&m);
        int d=exgcd(a,m,x,y);
        if(b%d)
            printf("impossible\n");
        else
        {
            printf("%d\n",(ll)x*(b/d)%m);
        }
    }
    return 0;
}


四、快速幂

1. 快速幂算法
typedef long long ll;
ll q_pow(int a,int b,int p)
{
    ll ans=1;
    while(b)
    {
        if(b&1) ans=(ll)ans*a%p;
        b>>=1;
        a=(ll)a*a%p;
    }
    return ans%p;
}


2. 大数快速幂(降幂公式)
/*当你要计算 A^B%C的时候 
如果B很大,达到10^100000,所以我们应该联想到降幂公式。  
 
降幂公式:A^B%C = A^(B%phi(C) + phi(C))%C 
分两种情况:  
当B<=phi(C)时,直接用快速幂计算A^B mod CB>phi(C)时,用快速幂计算A^(B mod phi(C)+phi(C)) mod C  
*/  
#include   
#include   
#include   
#include   
#include   
#include   
#include   
using namespace std;  
const int mod = 1e9+7;  
typedef long long ll;  
ll phi(ll n)   //求欧拉函数值   
{  
    int ans=n,temp=n;  
    for(int i=2;i*i<=temp;i++)   
    {  
        if(temp%i==0)   
        {  
            ans-=ans/i;  
            while(temp%i== 0) temp/=i;  
        }  
    }  
    if(temp>1) ans-=ans/temp;  
    return ans;  
}  
ll mod_pow(ll x,ll n,ll mod)  //快速幂   
{     
    ll ans=1;  
    while(n)   
    {  
        if(n%2==1) ans=ans*x%mod;  
        x=x*x%mod;  
        n/=2;  
    }  
    return ans;  
}  
ll a,c;  
char b[1000010];  
int main()   
{     
    while(scanf("%lld%s%lld",&a,b,&c)!=EOF)   
    {  
        c%=mod;  
        a%=mod;  
        ll phic=phi(mod);  
        int i,len=strlen(b);  
        ll res=0,ans;   
        for( i=0;i<len;i++)
        {
            res=res*10+b[i]-'0';
            if(res>phic)
            break;
        }
        if(i==len)
        {
            ans=mod_pow(a,res,mod)*c%mod;
        }
        else
        {
            res=0;
            for(int i=0;i<len;i++)
            {
                res=res*10+b[i]-'0';
                res%=phic;
            }
            ans=mod_pow(a,res+phic,mod)*c%mod;
        }
        cout<<ans<<endl;
    }  
    //cout<<mod_pow(2,3,mod);
    return 0;  
}

以上模板转自:大神博客



3. 快速幂求逆元(费马小定理)

费马小定理:如果p是一个质数,而整数a不是p的倍数,则有 a ^(p-1)≡1(mod p)

应用:(a / b) % p = a * q_pow( b, p-2 ) % p



五、欧拉函数

1. 直接求与n互质的数的个数:

根据定义求

//计算公式:phi[n]=n*(1-1/p1)*(1-1/p2)*...*(1-1/pn)=n*(p1-1)/p1*(p2-1)/p2*...(pn-1)/pn
int get_phi(int n)
{
    int res=n;
    for(int i=2;i<=n/i;i++)
    {
        if(n%i==0)
        {
            res=res/i*(i-1);
            while(n%i==0) n/=i;
        }
    }
    if(n>1)
        res=res/n*(n-1);
    return res;
}


2. 筛法求1-n每个数的phi值(欧拉函数)
#include
using namespace std;
const int N =1e6+5;
typedef long long ll;
bool isprime[N];
int prime[N];
int phi[N];
int cnt;
void euler()
{
    phi[1]=1;
    for(int i=2;i<=N;i++)
    {
        if(!isprime[i])
        {
            prime[cnt++]=i;
            phi[i]=i-1;
        }
        for(int j=0;prime[j]<=N/i;j++)
        {
            int t=prime[j]*i;
            isprime[t]=true;
            if(i%prime[j]==0)
            {
                phi[t]=prime[j]*phi[i];break;
            }
            phi[t]=(prime[j]-1)*phi[i];
        }
    }
}
int main()
{
    euler();
    return 0;
}


六、组合数学

1. a, b 范围在2000以内,100000组询问,求c(a,b)%(1e9+7)的值。

题目链接:Acwing 885.求组合数1

询问次数很大,所以需要预处理出来2000以内的所有c(a,b)
这里用到了一个递推式:c(a, b)=c(a-1,b-1)+c(a-1,b)
从前a个数里面挑b个数,它比a-1多的就是第a个数,那么如果b个数里面包含第a个数,就从前a-1个数里面挑b-1个数,如果不包含,就从前a-1个数里面挑b个数。分别对应等式右边的两个值。

#include
#include
#include
using namespace std;
const int mod=1e9+7;
const int N =2005;
int c[N][N];
void init()
{
    for(int a=0;a<=2000;a++)
    {
        for(int b=0;b<=a;b++)
        {
            if(b==0)
                c[a][b]=1;
            else
                c[a][b]=(c[a-1][b-1]+c[a-1][b])%mod;
        }
    }
}
int main()
{
    int n;
    cin>>n;
    init();
    while(n--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",c[a][b]);
    }
    return 0;
}


2. a, b范围在1e5以内,100000组询问,求c(a,b)%(1e9+7)的值。

题目链接:Acwing 886. 求组合数2

由于询问数量同样很大,所以也需要进行预处理,只不过不是直接处理出c(a,b)的值。
这里用到了求组合数的定义式:
数论知识及模板整理_第1张图片
那么我们直接处理出1-N的阶乘就好。需要注意的是a / b != (a%mod) / (b%mod)
那么我们需要求出分母的逆元,那么需要处理出1-N阶乘的逆元。(由于mod是个质数,可以运用小费马定理求逆元)

#include
#include
#include
using namespace std;
typedef long long ll;
const int N =1e5+10,mod=1e9+7;
ll fac[N],infac[N];
ll q_pow(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
            ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
void init()
{
    fac[0]=infac[0]=1;
    for(int i=1;i<N-5;i++)
    {
        fac[i]=fac[i-1]*i%mod;
        infac[i]=infac[i-1]*q_pow(i,mod-2)%mod;
    }
}
int main()
{
    init();
    int n;
    cin>>n;
    while(n--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%lld\n",fac[a]*infac[a-b]%mod*infac[b]%mod);
    }
    return 0;
}


3. a, b在1e18范围内,p在1e5范围内,求c(a,b)%p的值。

由于a,b非常大,而模数比较小,所以我们可以运用lucas定理求解。
lucas定理的内容是:c(a,b)%p = c(a%p, b%p) * c(a/p, b/p) %p

需要注意的是,如果a,b均小于p,那么可以直接运用定义求解

#include
#include
#include
using namespace std;
typedef long long ll;
ll p;
ll q_pow(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
            ans=ans*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ans;
}
ll C(ll a,ll b)
{
    ll ans=1;
    for(ll i=a,j=1;j<=b;i--,j++)
    {
        ans=ans*i%p;
        ans=ans*q_pow(j,p-2)%p;
    }
    return ans;
}
ll lucas(ll a,ll b)
{
    if(a<p&&b<p)
        return C(a,b);
    return C(a%p,b%p)*lucas(a/p,b/p)%p;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        ll a,b;
        cin>>a>>b>>p;
        cout<<lucas(a,b)<<endl;
    }
    return 0;
}


4. a, b在5000以内,输入a,b,求c(a,b)的值。

题目链接:Acwing 888. 求组合数4

题目没有要求让模某一个数字,那么就要输出一个很大很大的数字,需要用高精度进行运算。
但是直接进行高精度乘法和除法比较慢,而且很麻烦。于是我们用分解质因数的方法进行计算。
计算出每一个质因数在c(a,b)的分子中出现了多少次,分母中出现了多少次,两者相减,就是它对答案的贡献,最后用高精度乘法把它们乘起来就可以了。

#include
#include
#include
#include
using namespace std;
const int N = 5005;
int prime[N],isprime[N],sum[N],cnt;
void init(int n)
{
    for(int i=2;i<=n;i++)
    {
        if(!isprime[i])
        {
            prime[cnt++]=i;
            for(int j=i+i;j<=n;j+=i)
                isprime[j]=1;
        }
    }
}
int get_p(int n,int p)
{
    int ans=0;
    while(n)
    {
        ans+=n/p;
        n/=p;
    }
    return ans;
}
vector<int> mul(vector<int> &a,int b)
{
    int t=0;
    vector<int> ans;
    for(int i=0;i<a.size();i++)
    {
        t+=a[i]*b;
        ans.push_back(t%10);
        t/=10;
    }
    while(t)
    {
        ans.push_back(t%10);
        t/=10;
    }
    return ans;
}
int main()
{
    int a,b;
    cin>>a>>b;
    init(a);
    for(int i=0;i<cnt;i++)
    {
        int p=prime[i];
        sum[i]=get_p(a,p)-get_p(a-b,p)-get_p(b,p);
    }
    vector<int> ans;
    ans.push_back(1);
    for(int i=0;i<cnt;i++)
    {
        for(int j=0;j<sum[i];j++)
            ans=mul(ans,prime[i]);
    }
    for(int i=ans.size()-1;i>=0;i--)
        cout<<ans[i];
    return 0;
}


七、高斯消元

// a[N][N]是增广矩阵
int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r;
        for (int i = r; i < n; i ++ )   // 找到绝对值最大的行
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;

        if (fabs(a[t][c]) < eps) continue;

        for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]);      // 将绝对值最大的行换到最顶端
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];      // 将当前上的首位变成1
        for (int i = r + 1; i < n; i ++ )       // 用当前行将下面所有的列消成0
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];

        r ++ ;
    }

    if (r < n)
    {
        for (int i = r; i < n; i ++ )
            if (fabs(a[i][n]) > eps)
                return 2; // 无解
        return 1; // 有无穷多组解
    }

    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[i][j] * a[j][n];

    return 0; // 有唯一解
}

题目链接



八、容斥原理

模板题: ACwing 能被整除的数

给定一个整数n和m个不同的质数p1,p2,…,pm。

请你求出1~n中能被p1,p2,…,pm中的至少一个数整除的整数有多少个。

输入格式
第一行包含整数n和m。

第二行包含m个质数。

输出格式
输出一个整数,表示满足条件的整数的个数。

数据范围
1≤m≤16,
1≤n,pi≤109
输入样例:
10 2
2 3
输出样例:
7

#include
using namespace std;
typedef long long ll;
int a[20];
int main()
{
    int n,m;
    cin>>m>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    int ans=0;
    for(int i=1;i<(1<<n);i++)
    {
        ll t=1,cnt=0;
        for(int j=0;j<n;j++)
        {
            if(i>>j&1)
            {
                if(t*(ll)a[j]>m)
                {
                    t=-1;break;
                }
                t*=a[j];cnt++;
            }
        }
        if(t!=-1)
        {
            if(cnt%2)
                ans+=m/t;
            else
                ans-=m/t;
        }
    }
    cout<<ans<<endl;
    return 0;
}


九、中国剩余定理



十、其他数学结论

1. 威尔逊定理
内容:当且仅当p为素数时:( p -1 )! ≡ -1 ( mod p )
2. …


未完待续。。


更详细的数论模板请戳 ACwing数论模板

你可能感兴趣的:(数论,模板,学习笔记,算法)