同余&逆元
1. 同余
1. 同余的基本概念及性质
- 若\(x\)%\(m=a\)即m是 x-a 的一个因子, 则称x与a关于m同余,记作:\[x \equiv a(mod \;m)\]
- 同余基本性质:
○1. 自反性:\(a \equiv a(mod\;m)\)
○2. 对称性:\(a \equiv b(mod\;m) \rightarrow b \equiv a(mod\;m)\)
○3. 传递性:\(a \equiv b(mod\;m),b \equiv c(mod\;m) \rightarrow a \equiv c(mod\;m)\)
○4. 同加性:\(a \equiv b(mod\;m) c \equiv d(mod\;m) \rightarrow a+c \equiv b+d(mod\;m)\)
○5. 同乘性:\(a \equiv b(mod\;m) c \equiv d(mod\;m) \rightarrow ac \equiv bd(mod\;m)\)
○6. 同幂性:\(a \equiv b (mod\;m) \rightarrow a^n \equiv b^n(mod m)\)n是自然数
○7. 若\(a \equiv b(mod\;m),n|m\) 则 \(a \equiv b(mod\;n)\)
○8. 若\(ac \equiv bc(mod\;m),(c,m)=d\)则\(a \equiv b(mod\;\dfrac{m}{d})\)
○9. 若\((m,n)=1,a \equiv b(mod\;m),a \equiv b(mod\;n) \Leftrightarrow a \equiv b(mod\;mn)\)
2. 求解线性同余方程 \(ax≡c(mod\;b)\)
可转化为求解方程: \(ax+by=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.第二步:求解同余方程:\(ax≡gcd(a,b)(mod b)\)即\(ax+by=gcd(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=gcd(a,b)*x\)
令\(x=1,y=0\)即得最后的一组解。
考虑从后往前推出\(ax+by=c\)的解:
设我们前一步求出的解为\(x_1,y_1,此时a,b的值就表示为a,b,当前的解表示为x,y\)
那么因为\(gcd(a,b)=gcd(b,a\)%\(b),a\)%\(b=a-(a/b)*b\)有:\[b*x_1+(a-(a/b)*b)y_1=gcd(a,b)\]
则:
\[b*x_1+(a-(a/b)*b)y_1=ax+by\]
整理得:
\[ay_1+b(x_1-(a/b)y_1)=ax+by\]
容易看出:
\[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.求解单变量模线性方程组(中国剩余定理)
有如下方程:
\[\begin{cases}
x \equiv a_1 (mod\;m_1)\x \equiv a_2 (mod\;m_2)\x \equiv a_3 (mod\;m_3)\\dots\dots \dots\x \equiv a_n (mod\;m_n)\\end{cases}
\]
其中(\(m_1\;m_2\;m_3\;m_n\;\mbox{两两互质}\))
为了方便表示,将x设为S
(1)设\(M=\Pi^n_{i=1}m_i\), 设\(M_i=M/m_i\)
(2)可知对于每一个\(i有:(M_i,m_i)=1\)
即:
\(\qquad\qquad\qquad\qquad\qquad M_ix+m_iy=1\)
那么\(x为M_1\)的逆元,用\(t_i\)表示
两边同时扩大\(a_i倍\)
\(\qquad\qquad\qquad\qquad\qquad M_ia_it_i+m_ia_iy=a_i\)
而\(y\)的取值与求解无关,可将\(a_iy\)视为\(y\),则:
\(\qquad\qquad\qquad\qquad\qquad M_ia_it_i+m_iy=a_i\)
那么易知 \(S=M_ia_it_i\)
则原同余方程组通解为:
\[x=a_1t_1M_1+a_2t_2M_2+....+a_nt_nM_n+kM,k∈Z\]
为什么把每一个加起来就行了呢?
因为每一个\(M_i\)都含有因子\(m_j(j\ne 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则基于扩展欧基里德算法
做法:
还是如下方程:
\[\begin{cases}
x≡a_1 (mod\;m_1)\x≡a_2 (mod\;m_2)\x≡a_3 (mod\;m_3)\\dots\dots \dots\x≡a_n (mod\;m_n)\\end{cases}
\]
其中\(m_1\ m_2\ m_3\ m_4 \dots m_n\)不一定互质
我们可以发现左边的式子都是相同的,于是有了同余方程合并这种操作
既然是合并,我们只要讨论两个式子的时候的情况
对于:
\[\begin{cases}
x \equiv a_1\ (mod\ m_1)\x \equiv a_2\ (mod\ m_2)\\end{cases}
\]
可以看做是两个方程:
\[\begin{cases}
x =a_1+x_1m_1\x =a_2+x_2m_2\\end{cases}
\]
合并一下得到:\[a_1+x_1m_1=a_2+x_2m_2\]
移项:\[x_1m_1=a_2-a_1+x_2m_2\]
假定\(a_2 \geq a_1\),设为\(c\),再化为同余方程:\[m_1x_1\equiv c\ (mod\ m_2)\]
令\(gcd(m_1,m_2)=d\),该同于方程有解当且仅当\(d|c\),所以如果\(d\)不整除\(c\)则整个同余方程组无解
反之,由同余的性质得:\[\frac{m_1}{d}x_1\equiv \frac{c}{d}\ (mod \frac{m_2}{d})\]
记\(d_1=\dfrac{m_1}{d},d_2=\dfrac{m_2}{d},c_2=\dfrac{c}{d}\)
由于此时\(d_1,d_2\) 一定互质,所以\(d_1\)在模\(d_2\)的意义下一定有逆元,记为\(d_1^{-1}\),那么可以解出\(x_1\)(其实就是扩欧)
\[x_1=d_1^{-1}c_2+d_2x_2\]
回代进一开始的方程:
\[x=c+(d_1^{-1}c_2+d_2x_2)m_1\]
展开化简得:
\[x=d_1^{-1}c_2m_1+c+\frac{m_1m_2}{d}x_2\]
于是我们可以得到一个新的同余方程:
\[x\equiv d_1^{-1}c_2m_1+c \ (mod\ \frac{m_1m_2}{d})\]
于是就这样一直合并下去,最后的解就直接出来了(注意最后的模数会变成\(lcm(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;
}
Upd: 原来的模板太丑了,贴一个简洁一点的函数模板
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);
ll d=gcd(p1,p2);ll c=b2-b1;
if(c%d) return void(puts("no solution"));
ll d1=p1/d,d2=p2/d,lcm=p1/d*p2;// 这里根据的是负数也能取模 , 可以简化代码
c/=d;ll inv,y;exgcd(d1,d2,inv,y);
ll x1=Mul(inv,c,d2);
b1=(b1+Mul(x1,p1,lcm))%lcm;p1=lcm;
}
printf("%lld\n",b1%p1);
return;
}
5.卢卡斯定理(大组合数取模)
对于组合数取模,如\[C^m_n\ mod\ p\]
其中p是一个质数,有如下定理:
卢卡斯定理:组合数\(C^m_n\)在模意义下等价于把n和m看成一个p进制数,对每一位分别求出组合数后乘起来
比如说假设:
\(n=a_1*p^0+a_2*p^1+a_3*p^3+\dots a_k*p^k,m=b_1*p^0+b_2*p^1+b_3*p^3+\dots b_k*p^k\)
那么:\[C^m_n\; mod\; p=\prod_{i=0}^k C^{b_i}_{a_i} \; mod\;p\]
显然如果p很大的话没有什么鸟用
但是当p不是特别大的话,我们可以发现通过这个定理我们要求的组合数的n,m都不会超过p,可以使用阶乘来解决,并且这时阶乘一定和p是互质的,一定存在逆元,通过阶乘逆元我们可以直接算出组合数
复杂度是\(O(p\ log_pn)\),预处理阶乘逆元就直接是\(O(p+log_pn)\)了,看上去还是很有用的
主要代码:
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^m_n\ mod\ p\]
但是p不一定是质数
这个其实和卢卡斯定理也没有什么很大的关系
我们先把p给质因数分解:\[p=p_1^{k_1}p_2^{k_2}p_3^{k_3}\dots p_n^{k_n}\]
可以看出,如果所有的k都是1的话,我们设\(C_n^m=x\),对每一个\(p_i\)分别用卢卡斯定理求出组合数\(C_i\),那么就变成了求解一系列的同余方程组:
\[\begin{cases}
x \equiv C_1 \ mod\ p_1 \x \equiv C_2 \ mod\ p_2 \x \equiv C_3 \ mod\ p_3 \x \equiv C_4 \ mod\ p_4 \x \equiv C_5 \ mod\ p_5 \\end{cases}
\]
于是我们可以用中国剩余定理进行合并,求出最后的x,显然在模p意义下最后只会有唯一解
问题在于我们现在的p可能不是一次,而是有k次,要想用CRT来进行合并只能靠求出\(C_n^m\ mod\ p_i^{k_i}\)
因为要把同余式\(x\equiv a\ (mod\ p_1p_2)\)拆开必须要满足\(p_1\)和\(p_2\)互质,显然两个相同的数不会互质
于是问题转化为快速求出\(C_n^m\ mod\ p_i^{k_i}\)
为方便,我们设现在考虑的模数是\(p^k\),\(p\)是一个质数
还是考虑用阶乘来解决:\[C^m_n=\dfrac{n!}{m!(n-m)!}\]
我们仔细观察可以发现\(n!\)中含有的\(p\)这个因子的个数一定会不少于\(m!(n-m)!\)中p的个数,我们可以很容易得到一个阶乘中含有的p的个数的递推公式:\[f(n)=f(\lfloor { \frac{n}{p} }\rfloor)*\lfloor { \frac{n}{p} }\rfloor\]
例如我们要求:\(9!\)中\(2\)的个数:
\[9!=1\times2\times3\times4\times5\times6\times7\times8\times9 \=1\times3\times5\times7\times9\times[2\times(1\times2\times3\times4)]
\]
这就比较直观了,有了这个的话,我们假设求出阶乘中不含\(p\)的项的积,这样就可以通过逆元来进行组合数计算了,只需要最后把因该有的\(p\)给再乘上去就行了
于是问题再次变为如何快速求出阶乘
其实方法在上面\(9!\)的变换中就可以发现了,由于我们不管\(p\)有多少,发现提出来一个\(p\)之后,右边那部分的阶乘可以递归进行计算,就是\(\lfloor { \frac{n}{p} }\rfloor!\),于是关键在于计算左边
由于是模p意义下,在上面的式子中,可以发现左边其实全部都是1,手玩一下其他的发现显然这个东西是以p个一循环的,并且可能会剩下不超过p个数
所以循环部分算出一个然后快速幂\(n/p\)次,最后还会剩下\(n\%p\)个,直接暴力算这些
所以对于一次的阶乘要算的次数也不会超过\(O(p)\)次,总共有\(log_pn\)层
所以计算一个阶乘的复杂度为\(O(p\ log_pn)\)
总复杂度也就是把所有模数的复杂度加起来,最高也不超过最大质因子的复杂度
所以我们就解决了这个问题
剩下的就是算出逆元,求出组合数,处理多余的质因子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);
}
Upd: 这个以前写的板子真是丑到爆炸 , 写完一遍就不会了,所以再次放一个好看一点的模板
int FC[4][N],Pr[4]={0,3,11,100003},mo[4]={0,81,121,100003};//这里用于预处理到模数(不含其质因子)的阶乘,存放质因子和分解后模数
inline int gcd(int a,int b){return b? gcd(b,a%b):a;}
inline int Count(int n,int d){return n<d? 0:(n/d+Count(n/d,d));}//计算阶乘中的该因子个数
inline void Exgcd(int a,int b,int &x,int &y){
if(!b) {x=1;y=0;return;}
Exgcd(b,a%b,x,y);int tmp=x;x=y;y=tmp-a/b*y;return;
}
inline int Inv(int a,int mod){a%=mod;int x,y;Exgcd(a,mod,x,y);return (x+mod)%mod;}
inline int Fac(int n,int id){//阶乘
return n<=mo[id]? FC[id][n]:((ll)fpow(FC[id][mo[id]],n/mo[id],mo[id])*FC[id][n%mo[id]]%mo[id]*Fac(n/Pr[id],id))%mo[id];
}
inline int Comb(int n,int m,int id){//组合数
int facn=Fac(n,id),facm=Fac(m,id),facnm=Fac(n-m,id);
int Pop=fpow(Pr[id],Count(n,Pr[id])-Count(m,Pr[id])-Count(n-m,Pr[id]),mo[id]);
if(!Pop) return 0;
return (ll)facn*Inv(facm,mo[id])%mo[id]*Inv(facnm,mo[id])%mo[id]*Pop%mo[id];
}
inline int ExLucas(int n,int m){
if(n<m) return 0;if(!n&&!m) return 1;int ret=0;
for(int i=1;i<=3;++i) {int C=Comb(n,m,i);Inc(ret,(ll)C*Inv(mod/mo[i],mo[i])%mod*(mod/mo[i])%mod);}
return ret%mod;
}
//压行真的好看多了
2.逆元
1.定义:在 mod m 的意义下,设b是a的逆元,则:\(a*b≡1 (mod\;m)\)
2.求解方法:
1.线性递推法
逆元的递推公式:\[inv[i]=(P-P/i)*inv[P\%i]\%P\]
阶乘逆元的递推公式:\[inv[i]=inv[i+1]*(i+1)\%P\]
上面的\(inv[i]\)表示 \(i!\) 的逆元
2.扩展欧基里德法:
在上面我们用扩欧求了如下同余方程的解:\[ax \equiv b\ (mod\ p)\]
而逆元是求的这个同余方程:\[ax \equiv 1\ (mod\ p)\]
所以把b直接设为1然后扩欧解出x的最小正整数解就是a的逆元了
可以发现a,p一定要互质,不然同余方程无解,即a在a和p不互质的情况下是没有逆元的
3.费马小定理
费马小定理:
当a和p互质且p是质数时,有
\[a^{p-1} \equiv 1 \ (mod\ p)\]
相当于是:\[a^{p-2}*a \equiv 1\ (mod\ p)\]
所以\(a^{p-2}\)就是a的逆元了
4.欧拉定理
欧拉定理:
当a和p互质时,有
\[a^{\varphi(p)} \equiv 1\ (mod\ p)\]
所以\(a\)的逆元是\(a^{\varphi(p)-1}\),其实费马小定理是欧拉定理在p是质数时的一个特殊情况
补充:
扩展欧拉定理:\[a^b \equiv a^{b \%\varphi(p)+\varphi(p)}\ (mod\ p)\]
证明不会
小结:逆元在a和p互质的情况下才有
原文地址:https://www.cnblogs.com/NeosKnight/p/10391254.html