时间
最优化
初等数论
斐蜀定理(Bézout's lemma)
定理 1.1
对于两个整数
推论
整数
证明
运用辗转相除法去证明。
假设
由上面所述我们可以知道:
综上,推到最后发现所有的
还可以换种方式去理解斐蜀定理:
对于
【模板】裴蜀定理
对于两对数
线性丢番图方程
定义
方程
实际上就是在二维
定理 2.1
对于整数
方程
若知道方程的一个特解为
证明
两边同时除以
在
拆式子,得
等价移项并消掉
构造
综上所述,可以知道:
扩展欧几里得(exgcd)
求解方程
当前层为
下一层就要求解
移一下项:
到最后
最后求出来特解为
注意程序自定义函数中的
Code
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)
对于方程
我们由定理2.1可知:
当
设
接下来寻找
解一元一次不等式,得
那很明显的,在所有的正整数解(很重要,没有这个前提条件的话不一定成立)之中
由于
在所有正整数解中,
同余
定义
设
也可以说成
把
性质
同余的基本概念:若
将其转化为等式:其成立当且仅当存在整数
模
自反性:若
为整数,则 ; 对称性:若
为整数,且 ,则 ; 传递性:若
为整数,且 和 ,则 。
同余的加简乘除:
若
同加性:
; 同减性:
; 同乘性:
; 无同除性:同余的两边同时除以一个整数,不一定保持同余;
同幂性:若
和 是整数, ,且 ,则
定理 4.1
若
前半部分可以理解为
证明
前半部分很好理解,把这个同余方程转换为等式:
同样的对于等式去求出特解
注意:
推论
当
回到求解
[NOIP 2012 提高组] 同余方程
这题相当于在求整数
将
青蛙的约会
假设跳的次数为
设
中国剩余定理(Chinese Remainder Theorem, CRT)
引入
「物不知数」问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
即求同时满足以下条件的整数:除以
三人同行七十希,五树梅花廿一支,七子团圆正半月,除百零五便得知。
定理 5.1
有
有整数解,并且模
其中
证明
设
对于任意
故也有
另外,若
注意:有可能会爆long long,这里使用__int128来代替,输入输出要用快读快输。
Code
#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)
引入
上文所讲到的中国剩余定理的前提是所有的
实现
使用迭代法,每次合并两个同余式子,逐步操作,直到最后合并完所有式子,只剩下一个,就是最后的答案。
从头开始,将同余方程转为丢番图方程的形式:
合并两个等式:
移项:
用扩展欧几里得去求得一个特解
这里区分一下,数学上的
将
即:
其中
新的
Code
#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)
引入
求组合数的方法有很多种,其中用逆元的求法,是通过
定理
对于非负整数
其中,
编程时的卢卡斯定理表达:
证明
对于素数
也可以这样理解,因为
在上面的条件中
带入二项式定理中,得到:
令
若
其中需要保证
这里为什么要把
其实就可以把左边的
最后将两边第
最后展开利用递归实现,时间复杂度瓶颈在预处理求逆元部分,为
Code
#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;
}