基础数论大杂烩

exgcd

一,前置知识:

裴蜀定理:若有\(a,b\)\(a,b\)不全为0,则存在整数\(x,y\),使得\(ax+by=gcd(a,b)\)

二,算法过程:

作用 :给定\(a,b,c\),求解\(ax+by=c\)的整数解。

我们先考虑求解\(ax+by=gcd(a,b)\)

由于\(gcd(a,b)=gcd(b,a\%b)\),所以有:

\(\begin{cases}ax_1+by_1=gcd(a,b) \\ bx_2+(a\%b)y_2=gcd(b,a\%b)\end{cases}\)

联立,得到:\(ax_1+by_1=bx_2+(a\%b)y_2\)

由于\(a\%b=a-b\cdot\lfloor\frac{a}{b}\rfloor\)

化简后得到:\(ax_1+by_1=ay_2+b(x_2-\lfloor\frac{a}{b}\rfloor y_2)\)

所以说\(x_1=y_2,y_1=(x_2-\lfloor\frac{a}{b}\rfloor y_2)\)

递归求解!!!

边界条件:当\(b=0\)时,\(x=1,y=0\)

求出来了方程:\(ax+by=gcd(a,b)\),怎么求\(ax+by=c\)呢?

已知:\(ax+by=gcd(a,b)\),则:\(c\cdot\frac{a}{gcd(a,b)}x+c\cdot\frac{b}{gcd(a,b)}y=c\)

\(c|gcd(a,b)\)时,可以得到整数解,方程化简为\(ax_1+by_1=c\)

以此可以判断是否有解(gcd在求exgcd时顺便求出)

那么,怎么求出最大、最小正整数解呢。

\(x_1=\frac{c}{gcd(a,b)}\cdot x,y_1=\frac{c}{gcd(a,b)}\cdot y,a_1=\frac{a}{gcd(a,b)},b_1=\frac{b}{gcd(a,b)}\)

可得,\(a(x_1+kb_1)+b(y_1-ka_1)=c\)

所以通解就为:

\(\begin{cases}x=x_1+kb_1\\ y=y_1-ka_1\end{cases}\)

要求\(x,y\)的正整数解的个数,最大、最小正整数\(x,y\)。求出\(k\)的范围即可。

\(\begin{cases}x_1+kb_1>0 \\ y_1-ka_1>0\end{cases}\)

化简得:

\(\frac{-x_1}{b_1}<k<\frac{y_1}{a_1}\)

由于\(k\)为整数,那么\(\lfloor \frac{-x_1}{b_1} \rfloor <k< \lceil \frac{y_1}{a_1} \rceil\)

求解\(k\)即可。(注意c++中的精度问题)。

若没有合法的\(k\),则代表不存在一组正整数解,否则利用通解即可。

代码:(P5656 【模板】二元一次不定方程
#include<bits/stdc++.h>
#define ll long long
using namespace std;

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

int main(){
	int T;
	scanf("%d",&T);
	ll a,b,c;
	while(T--){
		scanf("%lld%lld%lld",&a,&b,&c);
		ll x,y;
		ll g=exgcd(a,b,x,y);
		//无解: 
		if(c%g){
			printf("-1\n");
			continue;
		}
		x=c/g*x,y=c/g*y;//求出来了一组给定的方程的一组特解。
		a/=g,b/=g;
		ll l=floor((double)-x/b),r=ceil((double)y/a);
		l++,r--;
		if(l<=r){//有正整数解 
			printf("%lld %lld %lld %lld %lld\n",r-l+1,x+l*b,y-r*a,x+r*b,y-l*a);
		}
		else{
			printf("%lld %lld\n",x+l*b,y-r*a);
		}
	} 
	return 0;
} 

参考资料:TJ-P5656

逆元

一,定义:

同余方程\(ax\equiv 1(mod~p)\)的解\(x\),被称为\(a\)的逆元,记作\(a^{-1},inv(a)\)

二,使用exgced求解逆元:

求解上述方程实际上就是在找\(ax-py=1\)的解,求解即可。(注意,若\(\gcd(a,p) \neq1\),则无解)。

三,使用费马小定理求解逆元:

费马小定理:若\(p\)为质数,那么\(a^{p-1}\equiv 1(mod~p)\)

此时\(inv(a)=a^{p-2}\)(快速幂求解)。

三,求解\(1!,2!,...n!\)的逆元:

有递推式:\(inv(n!)=(n+1)\cdot inv((n+1)!)\)

单独求出\(inv(n!)\),然后递推即可。

四,求解\(1,2,...,n\)的逆元:

法一:

有递推式:\(inv(i)=inv(i!)\cdot(i-1)!\)

先递推求出后面两个,然后递推求\(inv(i)\)

法二:

已知1的逆元为1。

\(p=i*a+b~(0\leq b<a)\)

\(i*a+b\equiv 0 (mod~p)\)

两边同时乘上\(i^{-1},b^{-1}\),得\(i^{-1}+a*b^{-1}\equiv 0~(mod~p)\)

所以,\(i^{-1}\equiv-a*b^{-1}~(mod~p)\)

\(a=\lfloor\frac{p}{i}\rfloor,b=p\%i\)

所以\(i^{-1}\equiv-\lfloor\frac{p}{i}\rfloor*(p\%i)^{-1}~(mod~p)\)

由于\(p\%i<i\),所以可以递推求得。

为了使得逆元为正整数,所以先加上一个\(p\)再模去

所以\(i^{-1}=(p-\lfloor\frac{p}{i}\rfloor)*(p\%i)^{-1}\)

代码:(P3811 【模板】模意义下的乘法逆元
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,p;
const int N=3000000+5;
ll inv[N];
int main(){
	scanf("%lld%lld",&n,&p);	
	inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=inv[p%i]*(p-p/i)%p; 
	for(int i=1;i<=n;i++) printf("%lld\n",inv[i]);
	return 0;
} 

五,求解\(a_1,a_2,...a_n\)的逆元:

\(s_i=\prod \limits_{j=1}^{i}a_j\)

显而易见:

\[\begin{align} &s_{i-1}^{-1}=s_i^{-1}\cdot a_i\\&a_i^{-1}=s_i^{-1}\cdot s_{i-1}\end{align} \]

用费马小定理求解\(s_n^{-1}\),然后递推即可。

代码:(P5431 【模板】模意义下的乘法逆元 2
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e6+6;
int n;
ll p,k,a[N];
ll jc[N],inv_jc[N];
inline void read(ll &x){
    char ch=getchar();ll f=1;
    while(ch<'0'||ch>'9'){
    	if(ch=='-') f=-1;
    	ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+(ch-'0');
		ch=getchar();
	}
	x*=f;
}
inline ll ksm(ll a,ll b){//a^b
    ll ans=1;
    while(b){
        if(b&1) ans=(ans*a)%p;
        a=a*a%p;
        b>>=1;
    }
    return ans%p;
}
int main(){ 
    scanf("%lld",&n);read(p);read(k);
    jc[0]=1;
    for(int i=1;i<=n;i++){
        read(a[i]);
        jc[i]=(jc[i-1]*a[i])%p; 
    }
    inv_jc[n]=ksm(jc[n],p-2);
    for(int i=n;i>=1;i--) inv_jc[i-1]=(inv_jc[i]*a[i])%p;
    ll ans=0;
    ll kk=k%p;
    for(int i=1;i<=n;i++){
        ans=(ans+(((jc[i-1]*inv_jc[i])%p)*kk)%p)%p;
        kk=kk*k%p;
    }
    printf("%lld\n",ans);
    return 0;
} 

中国剩余定理(CRT)

一,形式:

\(\begin{cases} x\equiv a_1(mod~m_1)\\x\equiv a_2(mod~m_2)\\...\\x\equiv a_n(mod~m_n)\end{cases}\)

\(m_i\)互相互质。

二,公式:

\(m=\prod \limits_{i=1}^{n}m_i,M_i=\frac{m}{m_i}~,~t_i\)表示\(M_i\)在模\(m_i\)意义下的逆元。

\(x\)的一组解为\(x=\sum_{i=1}^{n}a_iM_it_i\)

通解可表示为\(x+km\)

(证明见证明:数论四大定理之中国剩余定理

三,求解:

核心在于求解\(t_i\)

\(exgcd\)求解\(t_i\cdot M_i+y\cdot m_i=1\)即可

代码:(P1495 【模板】中国剩余定理(CRT)

#include<bits/stdc++.h>
#define int __int128
using namespace std;

inline int read(){
	char ch=getchar();
	int x=0,f=1;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f*x;
}
inline void write(int x){
	if(x<0) putchar('-'),x*=-1;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

int n,M=1;
const int N=12;
int a[N],m[N];
void exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
signed main(){	
	n=read();
	for(int i=1;i<=n;i++){
		m[i]=read(),a[i]=read();
		M*=m[i];
	}
	int ans=0,Mi;
	for(int i=1;i<=n;i++){
		int x,y;
		Mi=M/m[i];
		exgcd(Mi,m[i],x,y);
		ans=(ans+a[i]*Mi%M*x%M+M)%M;
	}
	write(ans);
	return 0;
} 

拓展中国剩余定理(exCRT)

一,形式:

\(\begin{cases} x\equiv a_1(mod~m_1)\\x\equiv a_2(mod~m_2)\\...\\x\equiv a_n(mod~m_n)\end{cases}\)

\(m_i\)不互相互质。

二,求解:

证明一:

我们假设已经求出来了前面\(k-1\)组的方程的最小整数解,\(x\)

\(m=lcm(m_1,m_2,...,m_{k-1})\)

则前面\(k-1\)组的方程的通解为\(x+km\)

考虑有一个整数\(t\),使得\(x+tm\equiv a_i~(mod~m_i)\)

化简后得:\(tm\equiv a_i-x~(mod~m_i)\)

相当于是在求方程\(tm+ym_i=a_i-x\)是否有解。

用exgcd求解即可。

若有解,则\(x^,=x+t\cdot m\)

当第一个方程时,直接求解即可。

证明二:

详见:扩展中国剩余定理(扩展CRT)详解

代码:(P4777 【模板】扩展中国剩余定理
#include<bits/stdc++.h>
#define int __int128
using namespace std;

inline int read(){
    char ch=getchar();
    int x=0,f=1;
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return f*x;
}
inline void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int exgcd(int a,int b,int &x,int &y){
    if(b==0){
        x=1,y=0;
        return a;
    }
    int g=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return g;
}
const int N=1e5+5;
int a[N],m[N],n;
signed main(){  
    n=read();
    for(int i=1;i<=n;i++){
        m[i]=read(),a[i]=read();
    }
    int M=1,x,y,g,ans=0;
    for(int i=1;i<=n;i++){
        g=exgcd(M,m[i],x,y);
        x*=((a[i]-ans)/g);
        ans=ans+x*M;
        M*=(m[i]/g);
        ans=(ans%M+M)%M;
    }
    write(ans);
    return 0;
} 

BSGS

一,形式:

求解\(a^x\equiv b~(mod~p)\)

二,求解:

\(t=\lceil sqrt(p)\rceil\)

\(x=At-B~(0\le B<t~,~0<A\le t)\)

\(a^{At-B}\equiv b~(mod~p)\)

移相,得:\(a^{At}\equiv b\cdot a^B~(mod~p)\)

枚举左右两边,用\(map\)判断是否有相等的即可。

代码:(P3846【模板】BSGS
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll p,b,n;
inline ll ksm(ll x,ll y){//x^y
	ll ans=1;
	while(y){
		if(y&1) ans=ans*x%p;
		x=(x*x)%p;
		y>>=1; 
	}
	return ans;
}
map<ll,ll> mp;
int main(){	
	scanf("%lld%lld%lld",&p,&b,&n);
	int t=sqrt(p)+1;
	ll bb=n%p;
	//枚举B: 
	for(int i=0;i<t;i++){
		mp[bb]=i;
		bb=(bb*b)%p;
	}
	ll czc=ksm(b,t);
	bb=1;
	//枚举A: 
	for(int i=1;i<=t;i++){
		bb=(bb*czc)%p;
		if(mp.find(bb)!=mp.end()){
			printf("%lld\n",t*i-mp[bb]);
			return 0;
		}
	}
	cout<<"no solution";
	return 0;
} 

Lucas定理

一,形式:

\(\tbinom{n}{m}\%p=\tbinom{n/p}{m/p}\tbinom{n\%p}{m\%p}\%p\)

适用于\(n,m\)较大,但\(p\)较小的情况下求解组合数

证明:算法学习笔记(25): 卢卡斯定理 - 知乎

二,求解:

我们利用lucas定理,递归求解即可。

注意边界条件,以及\(\tbinom{n\%p}{m\%p}\)中的\(n\%p,m\%p\)都在\([0,p-1]\),可以直接求解。

代码:(P3807 【模板】卢卡斯定理
#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=2e5+5;
ll n,m,p;
int T; 
ll jc[N];

inline ll ksm(ll a,ll b){//计算a^b 
	ll sum=1;
	while(b){
		if(b&1) sum=sum*a%p;
		a=a*a%p;
		b>>=1;
	}
	return sum;
}
inline ll C(ll x,ll y){//x选y 
	if(x<y) return 0;
	return (jc[x]*ksm(jc[y],p-2)%p*ksm(jc[x-y],p-2)%p+p)%p;
}
inline ll lucas(ll x,ll y){//x选y 
	if(!y) return 1;
	return (C(x%p,y%p)*lucas(x/p,y/p)%p+p)%p;
}

int main(){	
	scanf("%d",&T);
	while(T--){
		scanf("%lld%lld%lld",&n,&m,&p);
		jc[0]=1;
		for(int i=1;i<=n+m;i++) jc[i]=jc[i-1]*i%p;//注意,是n+m
		printf("%lld\n",lucas(n+m,n));
	}
	return 0;
} 

二项式反演

一,形式:

基本形式:

\(f(x)\)表示\(x\)集合的交集的大小,\(g(x)\)\(x\)个集合的补集的交集的大小

\[f(n)=\sum_{i=1}^n~(-1)^{i-1}~\tbinom{n}{i}~g(i)~\Leftrightarrow~ g(n)=\sum_{i=1}^n~(-1)^{i-1}~\tbinom{n}{i}~f(i) \]

常用形式1:

\(f(x)\)表示恰好满足\(x\)个方案数量,\(g(x)\)表示最多满足\(x\)个方案数量

\[f(n)=~\sum_{i=0}^{n}~(-1)^{n-i}~\tbinom{n}{i}~g(i)~\Leftrightarrow~ g(n)=~\sum_{i=0}^{n}~\tbinom{n}{i}~f(i) \]

常用形式2:

\(f(x)\)表示恰好满足\(x\)个方案数量,\(g(x)\)表示至少满足\(x\)个方案数量

\[f(k)=\sum_{i=k}^n~(-1)^{i-k}~\tbinom{i}{k}~g(i)\Leftrightarrow~ g(k)=\sum_{i=k}^n~\tbinom{i}{k}~f(i) \]

证明:二项式反演及其应用 - GXZlegend

(注:二项式反演可以拓展到多元的,详见:二项式反演-allenge高维反演的一个例子

欧拉筛求质数/欧拉函数

一,筛质数:

代码:

for(int i=2;i<=m;i++){
    if(!vis[i]) pri[++cnt]=i;
    for(int j=1;j<=cnt&&pri[j]*i<=m;j++){
        vis[pri[j]*i]=1;
        if(i%pri[j]==0) break;
    }
}

正确性/时间复杂度证明

二,求解欧拉函数

代码:

phi[1]=1;
for(ll i=2;i<N;i++){
    if(!vis[i]){
        pri[++cnt]=i;
        phi[i]=i-1;
    } 
    for(int j=1;j<=cnt&&i*pri[j]<N;j++){
        vis[i*pri[j]]=1;
        if(i%pri[j]==0){
            phi[i*pri[j]]=phi[i]*pri[j];
            break;
        }
        phi[i*pri[j]]=phi[i]*(pri[j]-1);
    }
}

正确性证明

posted @ 2024-01-27 11:38  123456xwd  阅读(2)  评论(0编辑  收藏  举报