Skip to content
0

时间

  • 2025/02/052025/02/10

最优化

  • 2025/02/06


初等数论

  • 2025/02/07

斐蜀定理(Bézout's lemma)

定理 1.1

对于两个整数 ab,有整数 xy 使得 ax+by=gcd(a,b)

推论

整数 ab 互素当且仅当存在整数 xy 使得 ax+by=1

证明

运用辗转相除法去证明。

假设 d=gcd(a,b),那么 d 必然是辗转相除后最后的一个非零的余数:

a=bq1+r1b=r1q2+r2r1=r2q3+r3rn2=rn1qn+d

由上面所述我们可以知道:

r1=abq1r2=br1q2=b(ab1)q2=baq2+bq1q2=aq2+b(q1q2+1)

综上,推到最后发现所有的 r 都可以表示为 ax+by=r 的形式,也就是说明 ax+by=d 成立。

还可以换种方式去理解斐蜀定理:

对于 x,yd=ax+byd 一定是 gcd(a,b) 的整数倍;最小的 dgcd(a,b)


【模板】裴蜀定理

对于两对数 (p,q) 的情况,Ap×Xp+Aq×Xq 的写法很类似于 ax+by。根据斐蜀定理,有整数 xy 使得 ax+by=gcd(a,b),也就是其最小值为 |gcd(a,b)|,不断合并即可。


线性丢番图方程

定义

方程 ax+by=c 称为二元线性丢番图方程,其中 a,b,c 为已知整数,x,y 是变量,问是否有整数解。

实际上就是在二维 xy 平面上的一条直线,这条直线上如果有整数坐标点,方程就有解;如果没有整数坐标点,就无解。如果存在一个解,就有无穷多个解。

定理 2.1

对于整数 abd=gcd(a,b)

方程 ax+by=c 有解的充分必要条件是 dc

若知道方程的一个特解为 (x0,y0),则通解为 x=x0+bd×k,y=x0+ad×k,其中 k 为任意整数。

证明

两边同时除以 d,则左边必然是整数,若想要式子成立,右边必然也需要时整数,所以 dc

ax0+by0=d 特解的基础上,寻找通解 a(x0+mk)+b(y0+nk)=d(其中 k 为任意整数):

拆式子,得 ax0+amk+by0+bnk=d

等价移项并消掉 d,得到 amk+bnk=0

构造 m,n 使得等式成立:

{m=bdn=ad

a×bd×k+b×(ad)×k=abkdabkd=0

综上所述,可以知道:

{x=x0+bd×ky=y0ad×k


扩展欧几里得(exgcd)

求解方程 ax+by=c 关键在于找到一个特解,由斐蜀定理可以知道 ax+by=d 必定有整数解,那么尝试使用扩展欧几里得算法求一个特解 (x0,y0)

当前层为 (a,b,x,y),这里 a>b,对于 ax+by=d,假设 a=bq+r,那么:

(bq+r)x+by=dbqx+rx+by=db(qx+y)+rx=d

下一层就要求解 b(qx+y)+rx=d,按照辗转相除,状态应该为 (b,a%b,y,x),设得到的解是 (x,y),则:

{x=yqx+y=x

移一下项:

{x=yy=xqx

到最后 b=0 时,那这时候 x=1,y=0 就是解。

最后求出来特解为 (x0,y0),那么 ax+by=c 的特解为:

{x0=x0×cdy0=y0×cd

注意程序自定义函数中的 xy 需要引用!!!

Code

cpp
inline int exgcd(int a,int b,int &x,int &y) {
	if(b==0) {
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return d;
}

【模板】二元一次不定方程 (exgcd)

对于方程 ax+by=c,先用婓蜀定理判断方程是否有解,若无解,则输出 1

我们由定理2.1可知:

{x=x0+bd×ky=y0ad×k

k 增大时,x 变大,y 变小。

addxaddy,也就是说:

{x=x0+dx×ky=y0dy×k

接下来寻找 x 的最小正整数解 xmin,那么 xmin1,即 x0dx1

解一元一次不等式,得 k1x0dx,注意这里是大于等于,所以要向上取整;

k 取到最大值有 xmin,所以 xmin=x01x0dx

那很明显的,在所有的正整数解(很重要,没有这个前提条件的话不一定成立)之中 xmin 对应的是 ymax,接下来考虑求 xmaxymin

由于 y 的通解为 y=y0dyk,已知 ymax,那么 ymin=ymaxmoddy,由于要避免取模出来有 0,则 ymin=(ymax1)moddy+1

在所有正整数解中,ymax 对应的是 xmin,易知从 xminxmaxymaxymink 的增减量是一样的,是 ymax1dy,这也是正整数解的数量,因为在这个过程中,xy 都一直保持 >0,那么 xmax=xmin+ymax1dy×dx


同余

定义

m 为正整数,若 ab 是整数,且 m(ab),则称 abm 同余。

也可以说成 a 除以 m 得到的余数,和 b 除以 m 的余数相同;或者说 ab 除以 m,余数为 0

abm 的同余记为 ab(modm)

性质

同余的基本概念:若 ab 为整数,m 为正整数,则 ab(modm) 当且仅当 amodm=bmodm

将其转化为等式:其成立当且仅当存在整数 k 使得 a=b+km。这也说明了同余方程和线性丢番图方程的关系。

m 的同余满足以下基本性质:

  • 自反性:若 a 为整数,则 aa(modm)

  • 对称性:若 a,b 为整数,且 ab(modm),则 ba(modm)

  • 传递性:若 a,,c 为整数,且 ab(modm)bc(modm),则 ac(modm)

同余的加简乘除:

c 是整数,且 cd(modm)

  • 同加性:a+cb+d(modm)

  • 同减性:acbd(modm)

  • 同乘性:a×cb×d(modm)

  • 同除性:同余的两边同时除以一个整数,不一定保持同余;

  • 同幂性:若 a,b,km 是整数,k,m>0,且 ab(modm),则 akbk(modm)

定理 4.1

a,bm 为整数,m>0,设 d=gcd(a,m)

d 不能整除 b,则同余方程 ab(modm) 无解;反之,db,则有 d 个不同余的解。

前半部分可以理解为 ab(modm) 有解的充分必要条件是 db

证明

前半部分很好理解,把这个同余方程转换为等式:ax+my=b,再运用定理 2.1,可得这个等式有解,条件是 db

同样的对于等式去求出特解 (x0,y0) 的通解,x=x0+md×k,但是这里是同余方程,k 是有范围的,当 0k<dd 个模 m不同余解。利用剩余系的概念解释,k 取遍了模 d 的完全剩余系。

注意:x0,y0 不一定是最小的解!!!

推论

am 互素的时,因为 d=1,所以线性同余方程 axb(modm) 有唯一的模 m 不同余解。

回到求解 axb(modm) 中未知数 x 的问题,首先求逆元,然后利用逆元求得 x


[NOIP 2012 提高组] 同余方程

这题相当于在求整数 a 在模 b 意义下的逆元,不保证 ab 互素,并不能使用费马小定理去解决问题;

ax1(modb) 转化为等式方程 ax+by=1,尝试用扩展欧几里得求解出这个方程的一个特解 (x0,y0),通解是 x=x0+bk,然后通过取模计算得出最小整数解。

青蛙的约会

假设跳的次数为 t,则 xy=(nm)t+Lk,其中 k 为任意整数。

nm=a,t=x,L=b,k=y,xy=c,那么就是 ax+by=c,用扩展欧几里得求出特解 x0 像上面取模计算得出最小整数解即可。


中国剩余定理(Chinese Remainder Theorem, CRT)

【模板】中国剩余定理(CRT)/ 曹冲养猪

引入

「物不知数」问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

即求同时满足以下条件的整数:除以 32,除以 53,除以 72

三人同行七十希,五树梅花廿一支,七子团圆正半月,除百零五便得知。

2×70+3×21+2×15=233=2×105+23,故解为 23

定理 5.1

m1,m2,,mrr 个两两互素的正整数,则同余方程组:

xa1(modm1)xa2(modm2)xak(modmr)

有整数解,并且模 M=i=1rmi 唯一,解为:

x(a1M1M11+a2M2M21++arMrMr1)(modM)

其中 Mi=MmiMi1Mi 在模 mi 下的逆元。

证明

ci=MiMi1(不对 mi 取模),我们需要证明对于所有 i,i[1,r] 都能满足 xai(modmi)

对于任意 i,jij,都有 Mj0(modmi),因为 Mj=Mmj,M=i=1rmi,也就是 Mj 的所有乘数中 mi,同余方程成立;

故也有 cjMj0(modmi)。又因为 mi 两两互素,有 ciMi(Mi1modmi)1(modmi),所以:

xk=1rakck(modmi)aiMi(Mi1modmi)(modmi)ai(modmi)

另外,若 xy,总存在 i 使得 xy 在模 mi 下不同余,故系数列表 {ai}x 是一一映射关系,方程组总是有唯一的解。

注意:有可能会爆long long,这里使用__int128来代替,输入输出要用快读快输。

Code
cpp
#include<bits/stdc++.h>
#define int __int128
using namespace std;
const int N=22;
inline int read() {
    int x=0;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) ;
    for(;isdigit(c);c=getchar())
        x=x*10+c-48;
    return x;
}
inline void write(int x) {
    if(x>9)
        write(x/10);
    putchar(x%10+48);
    return ;
}
int n,a[N],b[N];
inline int exgcd(int a,int b,int &x,int &y) {
    if(b==0) {
        x=1,y=0;
        return a;
    }
    int d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
inline int CRT(int a[],int m[],int n) {
    int p=1,M[N];
    for(int i=1;i<=n;i++)
        p*=m[i];
    for(int i=1;i<=n;i++)
        M[i]=p/m[i];
    int X=0;
    for(int i=1;i<=n;i++) {
        int x,y;
        int d=exgcd(M[i],m[i],x,y);
        x=(x%m[i]+m[i])%m[i]; 
        X=(X+a[i]*M[i]%p*x%p)%p;
    }
    return X;
}
signed main() {
    n=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[i]=read();
    write(CRT(b,a,n));
    return 0;
}

扩展中国剩余定理(EXCRT)

【模板】扩展中国剩余定理(EXCRT)

引入

上文所讲到的中国剩余定理的前提是所有的 m 是两两互素的,那么若模数 m 两两不互素怎么办呢?

实现

使用迭代法,每次合并两个同余式子,逐步操作,直到最后合并完所有式子,只剩下一个,就是最后的答案。

xa1(modm1)xa2(modm2)xak(modmr)

从头开始,将同余方程转为丢番图方程的形式:

x=a1+Xm1x=a2+Ym2

合并两个等式:

a1+Xm1=a2+Ym2

移项:

Xm1+(Y)m2=a2a1

用扩展欧几里得去求得一个特解 X0,很显然,在取模下,X 的解是唯一的,且是非负整数,那么就有:

X=(X0c/d)modb/d

这里区分一下,数学上的 mod 正整数的结果是非负整数;然而在程序运行中,负整数取模正整数数是负整数,所以需要加上模数再取模!!

X 代回 x=a1+Xm1 中求得原等式的一个特解 x,合并后新的等式为:

x=a+Xm

即:

xa(modm)

其中 m=m1m2gcd(m1,m2),a=x,再细细品味其中的妙处。

新的 m 的求解就很好说明了在普通中国剩余定理中的 M=i=1rmi,所有 mi 互素,所以最小公约数为 1,可以省略掉,蕴含的思想是由特殊到一般?...

Code
cpp
#include<bits/stdc++.h>
#define int __int128
using namespace std;
const int N=1e5+10;
inline int read() {
	int x=0;
	int c=getchar();
	for(;!isdigit(c);c=getchar()) ;
	for(;isdigit(c);c=getchar())
		x=x*10+c-48;
	return x;
}
inline void write(int x) {
	if(x>9)
		write(x/10);
	putchar(x%10+48);
	return ;
} 
int n,a[N],b[N],x,y;
inline int exgcd(int a,int b,int &x,int &y) {
	if(b==0) {
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return d;
}
inline int mul(int a,int b,int m) {
	int res=0;
	while(b) {
		if(b&1)
			res=(res+a)%m;
		a=(a+a)%m,b>>=1;
	}
	return res;
}
signed main() {
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),b[i]=read();
	if(n==1) {
		int A=1,B=a[1],C=b[1];
		int d=exgcd(A,B,x,y);
//		if(C%d!=0) {
//			printf("-1\n");
//			return 0;
//		}
		x=(x*(C/d)%(B/d)+(B/d))%(B/d);
		write(x);
		return 0;
	}
	int a1=b[1],m1=a[1],ans;
	for(int i=2;i<=n;i++) {
		int a2=b[i],m2=a[i];
		int A=m1,B=m2,C=((a2-a1)%m2+m2)%m2;
		int d=exgcd(A,B,x,y);
//		if(C%d!=0) {
//			printf("-1\n");
//			return 0;
//		}
//		x=mul(x,C/d,B/d);
		x=(x*(C/d)%(B/d)+(B/d))%(B/d);
		ans=(a1+x*m1);
		m1=m1*m2/d;
		ans=(ans%m1+m1)%m1;
		a1=ans;
	}
	write(ans);
	return 0;
}

卢卡斯定理(Lucas)

【模板】卢卡斯定理/Lucas 定理

引入

求组合数的方法有很多种,其中用逆元的求法,是通过 Cnrmodm=n!r!(nr)!modm,但是若 m 小于 n,那就不能保证 r!(nr)! 在模 m 下有逆元了;然而用杨辉三角的话,时间复杂度为 O(n2),不够优秀,那么我们就需要用到卢卡斯定理了!!!

定理

对于非负整数 n,r 和素数 m,有:

Cnri=0kCniri(modm)

其中,n=i=0knimir=i=0krimi,相当于是把 nr 表示为 k 位的 m 进制数,对每位分别进行计算组合数,最后乘起来。

编程时的卢卡斯定理表达:

CnrCnmodmrmodm×Cn/mr/m(modm)

证明

对于素数 m,r(0,m),有:

Cmr=m!r!(mr)!=(m1)!(r1)!(mr)!×mr=Cm1r1×mr0(modm)

也可以这样理解,因为 Cmr 为整数,rmr 取不到 m,所以 m! 必然进行运算后必然会剩下一个 m,所以在模 m 的意义下与 0 同余。

在上面的条件中 r 是不能取到 0m 的,因为 Cm0Cmm 的组合数为 110(modm)

带入二项式定理中,得到:

(1+x)m=r=0mCmr×xr=1+r=1m1Cmr+xm1+xm(modm)

n=qm+a,有:

(1+x)n=(1+x)qm+a=(1+x)qm(1+x)a(1+xm)q(1+x)a(modm)(i=0qCqi×xi)×(j=0aCaj×xj)(modm)

r>m,则 Crm=0,那么我们可以把式子等价换成:

r=0nCnr×xr(i=0qCqi×xi)×(j=0m1Caj×xj)(modm)

其中需要保证 im+jn

这里为什么要把 a 换成 m1 呢?

其实就可以把左边的 r[0,n] 在右边用所有数表示了,即 r=im+j,更便于理解。

最后将两边第 xr 次的系数取出来,根据 r=im+j,i=r/m,j=rmodm,a=nmodm,q=n/m,那就是:

CnrCqi×Caj(modm)Cn/mr/m×Cnmodmrmodm(modm)

最后展开利用递归实现,时间复杂度瓶颈在预处理求逆元部分,为 O(nlogn),其实可以优化预处理,考场再用吧...

Code
cpp
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int T,a,b,m,fac[N],inv[N];
inline int qpow(int a,int p) {
    int res=1;
    while(p) {
        if(p&1)
            res=res*a%m;
        a=a*a%m,p>>=1;
    }
    return res;
}
inline void init_() {
    fac[0]=inv[0]=1;
    for(int i=1;i<N;i++)  {
        fac[i]=fac[i-1]*i%m;
        inv[i]=inv[i-1]*qpow(i,m-2)%m;
    }
    return ;
}
inline int C(int n,int r) {
    if(r>n)
        return 0;
    return fac[n]*inv[n-r]%m*inv[r]%m;
}
inline int Lucas(int n,int r,int m) {
    if(r==0)
        return 1;
    return C(n%m,r%m)*Lucas(n/m,r/m,m)%m;
}
signed main() {
    scanf("%lld",&T);
    while(T--) {
        scanf("%lld%lld%lld",&a,&b,&m);
        init_();
        printf("%lld\n",Lucas(a+b,a,m));
    } 
    return 0;
}

最近更新