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\)即可
#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\)。
当第一个方程时,直接求解即可。
证明二:
代码:(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\)较小的情况下求解组合数
二,求解:
我们利用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) \](注:二项式反演可以拓展到多元的,详见:二项式反演-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);
}
}
标签:gcd,求解,数论,ll,基础,大杂烩,int,equiv,mod
From: https://www.cnblogs.com/123456xwd/p/17991265