【OI】剩余(入门)
gcd
定义
\(a,b \in \Z\),记 \(a\) 和 \(b\) 的最大公约数为 \(\gcd(a,b)\),最小公倍数为 \(\operatorname{lcm}(a,b)\)。
约定 \(\gcd(a,0)=a\),\(\gcd(0,0)=0\)。
如何求 \(\gcd(a,b)\)?
欧几里得算法
若 \(d \mid a,b\),则有 \(d \mid a \bmod b\),于是 \(\gcd(a,b)=\gcd(b,a \bmod b)\),称为辗转相除法,可以递归计算。
复杂度为 \(O(\log \max(a,b))\)。
证明:
如果一次运算会使得某个数值减半,则它为对数复杂度。
若 \(a<b\),则 \(\gcd(a,b)=\gcd(b,a)\)。
若 \(a>b\),则 \(a \bmod b\) 至少会让 \(a\) 减半。
得证。
值得注意的是,用欧几里得算法去算斐波那契数列的相邻两项会使复杂度达到最劣。
Stein算法
取模计算速度太慢了,如果换成减法和位运算就会快上很多。
更相减损术:\(\gcd(a,b)=\gcd(b,a-b)\),与辗转相除法同理。
以下为 Stein 算法的过程:
-
若 \(a \bmod 2=1,b \bmod 2=1\),则 \(\gcd(a,b)=\gcd(b,a-b)\)。
-
若 \(a \bmod 2=1,b \bmod 2=0\),则 \(\gcd(a,b)=\gcd(\frac{a}{2},b)\),\(b\) 也同理。
-
若 \(a \bmod 2=0,b \bmod 2=0\),则 \(\gcd(a,b)=2 \gcd(\frac{a}{2},\frac{b}{2})\)。
除法可以用位运算代替。
同理,根据减半原则,时间复杂度为 \(O(\log \max(a,b))\)。
#define ctz __builtin_ctzll
inline int gcd(int a, int b){
if(!a||!b) return a|b;
if(a==1||b==1) return 1;
int az=ctz(a),bz=ctz(b),z=min(az,bz),t;
b>>=bz;
while(a) a>>=az,t=a-b,az=ctz(t),b=min(a,b),a=abs(t);
return b<<z;
}
值得注意的是,用这个可以过基于值域预处理的gcd这道题,可见效率之高。
exgcd
二元线性丢番图方程:形如 \(ax+by=c\) 的方程,其中 \(a,b,c \in \Z\)。
显然,这个方程要么无整数解,要么有无数组整数解。
如何判断这个方程有无整数解?
给出结论:方程有整数解的充要条件是 \(\gcd(a,b) \mid c\)。
证明:
必要性:若方程有整数解,则 \(\gcd(a,b) \mid ax\),\(\gcd(a,b) \mid by\),于是 \(\gcd(a,b) \mid ax+by=c\)。
充分性:
即裴蜀定理,首先考虑方程 \(ax+by=\gcd(a,b)\),需证 \(\exist x,y \in \Z\),使方程成立。
令集合 \(S=\{k \mid ax+by=k,x,y \in \Z\}\),令 \(s\) 为集合的最小正元素,此时 \(ax_0+by_0=s\)。
首先 \(\gcd(a,b) \mid s\)。
其次令 \(a=ps+q,q=a \bmod s \in [0,s)\),则 \(q=a-ps=a(1-px_0)+b(-py_0)\),于是 \(q \in S\),但是 \(s\) 为最小正元素,所以\(q=0\),可得 \(s \mid \gcd(a,b)\)。
综上,\(s=\gcd(a,b)\),此时 \(x=x_0,y=y_0\)。
那么如何求得这个方程的解呢?
首先考虑解 \(ax+by=\gcd(a,b)\)。
构造两个方程:
根据辗转相除法,有:
注意到 \(a \bmod b=a-\lfloor \frac{a}{b} \rfloor b\),
所以:
得 \(x_1=y_2,y_1=x_2-\lfloor \frac{a}{b} \rfloor y_2\)。
这个可以在辗转相除中递归求解,这样我们就得到了一组特解,然后用通解公式得到所有解。
inline int exgcd(int a, int b, int &x, int &y){
if(!b){
x=1,y=0;
return a;
}
int d=exgcd(a,b,y,x);
y-=a/b*x;
return d;
}
逆元
对于 \(a,p \in \Z\),称满足 \(ax \equiv 1 \pmod p\) 的 \(x\) 为 \(a\) 在模 \(p\) 意义下的乘法逆元,记为 \(a^{-1}\),只有 \(\gcd(a,p)=1\) 即 \(a \bot p\) 时,\(a\) 才存在逆元。
证明:
设 \(d= \gcd(a,p)\),则 \(d \mid ax \bmod p\)。
费马小定理
若 \(p \in P\),则 \(a^{p-1} \equiv 1 \pmod p\)。
于是 \(a \times a^{p-2} \equiv 1 \pmod p\),即 \(a^{-1} \equiv a^{p-2} \pmod p\)。
使用快速幂求,复杂度 \(O(\log p)\)。
exgcd
若 \(p\) 不为素数,无法使用费马小定理。
然后用 exgcd 求 \(x\),复杂度 \(O(\log a)\)。
线性求连续 \(1\) ~ \(n\) 的逆元
首先 \(1^{-1} \equiv 1 \pmod p\)。
考虑 \(i^{-1}\),令 \(k = \lfloor \frac{p}{i} \rfloor\),\(j = p \bmod i\),于是 \(ki+j \equiv 0 \pmod p\)。
因此 \(kj^{-1}+i^{-1} \equiv 0 \pmod p\),即 \(i^{-1} \equiv -kj^{-1} \pmod p\)。
这个 \(j\) 一定小于 \(i\),于是我们可以递推求逆元,复杂度 \(O(n)\)。
线性求任意 \(n\) 个数的逆元
给定序列 \(a\),求每个数的逆元。
记 \(s_i= \prod_{j=1}^i a_i\),先用费马小定理或 exgcd 求 \(s_n^{-1}\),记为 \(sv_n\),然后后缀处理出所有 \(sv_i\)。
\(a_i^{-1} \equiv s_{i-1} \times sv_i \pmod p\)
复杂度 \(O(n+\log p)\)。
同余方程组
考虑下面这样一个由多个线性同余方程组成的方程组:
该如何求解?
CRT
考虑 \(\forall m_i\) 两两互素的情况。
- 中国剩余定理(CRT):令 \(M=\prod m_i,M_i=\frac{M}{m_i}\),有:
其中 \(M_i^{-1}\) 表示 \(M_i\) 在模 \(m_i\) 下的逆元。
证明:
我们需要证明上面这个式子对每条方程都成立。
考虑一个 \(m_i\),当 \(j \neq i\) 时,\(m_i \mid M_j\),取模后为 \(0\);当 \(j=i\) 时,\(M_iM_i^{-1} \equiv 1 \pmod {m_i}\),则 \(a_iM_iM_i^{-1} \equiv a_i \pmod {m_i}\),于是得证。
但是如果你是个正常人的话,你就会觉得这玩意很蠢。
两两互素,什么雷霆条件?
因此 CRT 在 OI 里一点用都没有,它可以被更通用的 exCRT 平替。
exCRT
接下来考虑 \(m_i\) 不满足两两互素的情况。
先考虑两个方程:
改写成等式:
这是一个二元一次丢番图方程,用 exgcd 解,令 \(a=m_1,b=-m_2,c=a_2-a_1,d=\gcd(m_1,m_2)\),求出方程 \(aX+bY=c\) 的特解 \(X_0\)。
根据通解公式 \(X=X_0\frac{c}{d}+n \frac{b}{d}\),直接代入第一个等式:
这样我们就得到一个新的同余方程,我们成功把两个同余方程合并成一个,于是对于多个同余方程,一直重复合并即可,时间复杂度 \(O(n \log V)\)。
#include<bits/stdc++.h>
#define int __int128
#define inf 1e18
#define gc getchar
#define pc putchar
#define rg register
#define LB lower_bound
#define UB upper_bound
#define PII pair<int, int>
#define PDI pair<double, int>
#define fi first
#define se second
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using db=double;
using namespace std;
inline int read(){
int x=0,f=1;
char ch=gc();
while(!isdigit(ch)){
if(ch=='-') f=-f;
ch=gc();
}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
return x*f;
}
inline void write(int x){
if(x<0) pc('-'),x=-x;
if(x>9) write(x/10);
pc(x%10+'0');
}
const int N=114514;
int n,a[N],m[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,x,y);
int tmp=y;
y=x-a/b*y;
x=tmp;
return d;
}
inline int exCRT(){
int x,y,m1=m[1],a1=a[1];
int ans=0;
for(rg int i=2;i<=n;i++){
int m2=m[i],a2=a[i];
int A=m1,B=m2,C=a2-a1;
int d=exgcd(A,B,x,y);
if(C%d) return -1;
x=(x*(C/d)+(B/d))%(B/d);
ans=a1+x*m1;
m1=m2/d*m1;
ans=(ans%m1+m1)%m1;
a1=ans;
}
return ans;
}
signed main(){
// freopen("114514.in","r",stdin);
// freopen("114514.out","w",stdout);
n=read();
for(rg int i=1;i<=n;i++) m[i]=read(),a[i]=read();
write(exCRT());
return 0;
}

浙公网安备 33010602011771号