古典叙事 · 技术札记

序章

山河有卷
人间有声

写代码,也写长风、旧城与未熄的灯。
愿每一篇随笔,都有自己的山水与回声。
阅览随笔

扩展中国剩余定理

扩展中国剩余定理

使用场景

跟中国剩余定理相同,假设现在有多个同余线性方程,要你求解满足线性方程的最小正整数解,但是不保证每一个方程组的模数互质。
这时候我们就不能简单地用中国剩余定理直接计算了,思考一下,假如前k个,我们找到了答案x,想要继续满足第k+1个,我们需要让x不断地加上前k个模数的lcm,直到他满足新条件,显然,我们用枚举去求解时间复杂度过高,此时我们就可以用扩展欧几里得来快速的计算出来这个答案。

来看式子

为了不破坏前面已经满足的条件,我们只能在 nowx 的基础上,加上若干次 nowm。假设这个“若干次”是 \(k\) 次,那么新的数字就是:\(nowx + k \cdot nowm\)。为了让它满足新条件,它必须满足:$$nowx + k \cdot nowm \equiv b_i \pmod{a_i}$$我们把同余符号扒掉,变成等式(假设商是 \(y\)):$$nowx + k \cdot nowm = b_i + y \cdot a_i$$移项整理一下,把未知数 \(k\)\(y\) 留在一边:$$nowm \cdot k - a_i \cdot y = b_i - nowx$$你看!这句话翻译成数学语言,完完全全就是一个标准形态的二元一次不定方程 \(Ax + By = C\)!其中我们需要求的,就是这个 \(k\)(也就是你代码里的 x)。
然后我就用exgcd来求解出一个满足的最小解x1,然后把他乘上右边的数除以gcd(a,b),这样的话就可以,计算出一个合法解,然后我们再把这个数变成一个正数,再把它带回我原本的式子,就可以计算出来一个最小的合法答案

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

i64 n,g,m,p,t;
i64 a[300005],b[300005];
const i64 mod1=999911659;
i64 ksm(i64 a,i64 b,i64 mod){
	i64 res=1;
	while(b){
		if(b&1)res=res*a%mod;
		a=a*a%mod;
		b=b>>1;
	}
	return res;
}
i64 gcd(i64 a,i64 b){
	if(b==0)return a;
	return gcd(b,a%b);
}
i64 lcm(i64 a,i64 b){
	if(a==0||b==0)
	return max(a,b);
	return a/gcd(a,b)*b;
}
void exgcd(i64 a,i64 b,i64& x,i64& y){
	if(b==0){
		x=1;
		y=0;
		return;
	}
	i64 x1,y2;
	exgcd(b,a%b,x1,y2);
	x=y2;
	y=x1-a/b*y2;
}
void solve(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i];
	}
	i64 nowm=0,nowx=0;
	i64 x,y;
	nowx=b[1];
	nowm=a[1];
	for(int i=2;i<=n;i++){
		exgcd(a[i],nowm,x,y);//求解一个最小x
		i64 h=nowm/gcd(nowm,a[i]);
		x=(i64)((x*__int128(nowx-b[i])/gcd(nowm,a[i]))%h+h)%h;//求解一个最小合法解x
		nowm=lcm(nowm,a[i]);//扩大一下最小公倍数
		
		nowx=((__int128)x*a[i]+b[i])%nowm;//带入原本的式子,求出一个最小正数解
		
	}
	cout<<nowx<<'\n';
	
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	solve();
}
posted @ 2026-07-28 20:39  Morphis‘  阅读(8)  评论(0)    收藏  举报