前言
与 这个 定理 搏斗了 一天终于 把它kill 了 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
进入正题
题目描述
给定 n 组 非负整数 ai,bi,求解关于 x 的方程组
x ≡ a1 (mod b1)
x ≡ a2 (mod b2)
.
.
x ≡ an (mod bn)
声明 : M 表示 前 i-1 个bi 的 最小公倍数(LCM), ans 表示 满足前 i-1 个方程的最小解
初始化: 特判 第一组方程 令 M = b1 , ans = a1(第一组的话 b1的最小公倍数就是自己,只看第一个方程 ans 就等于 a1)
从第二组开始:
LCM(b1,b2) = b1×b2 /(gcd(b1,b2))这个不需要证明
因为 此时 M = b1 ,所以原式可化为:
LCM(M,b2) = M×b2 /(gcd(M,b2))
考虑: ans + t×M 一定是 前 i-1 个方程 的通解,因为 ans 是 最小解 所以 ans 加上前 i-1 个方程 bi 的最小公倍数 一定是前 i-1 个方程 的通解。(其中 t ∈ Z)
所以 如果要使得 ans 也满足 下一个方程的话(以第二个 来举例):
ans + t×M ≡ a2 (mod b2) 此时 ans 是a1 是已知的
移项得 : t×M ≡ a2 - ans (mod b2)(这一看就是 扩展欧几里得)(ax ≡ c (mod b))
所以 此时 t 就相当于是 x ,M 相当于是a ,a2 - ans 就相当于是c
所以利用扩展欧几里得 可以 解得t
但是 我们 要考虑 一个问题 扩展欧几里得 的操作
也就是说 我们解得的t 其实是 t × M ≡ gcd(M,b2) (mod b2) 的解
所以 a2 - ans 到 gcd(M,b2) 相当于是乘了一个 gcd(M,b2)/(a2 - ans) 所以我们得到的解 也相当于是 乘了 一个gcd(M,b2)/(a2 - ans)
那么真正的 t 应该是 t/(gcd(M,b2)/(a2 - ans))
也就是 t * (a2 - ans)/gcd(M,b2)
那么 对于下一次 求解来说
更新答案 : ans = ans + t * M,然后 ans = (ans%M + M) % M 保证ans 是最小的正整数解
因为 LCM(M,b2) = M×b2 /(gcd(M,b2)) 所以 M *= b2 /(gcd(M,b2))。
同理 循环 n - 1 次 得到最小的 ans 也就是要求的 x
下面贴代码
#include <cstdio> #include <cctype> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; const int maxn = 100005; int n; LL ai[maxn],bi[maxn]; LL quickmul(LL a,LL b,LL mod)// 这是龟速乘 { LL res = 0; while(b) { if(b & 1) res = (res + a) % mod; a = (a + a) % mod; b >>= 1; } return res % mod; } LL exGcd(LL a,LL b,LL &x,LL &y)// 扩欧 { if(!b) { x = 1; y = 0; return a; } LL r = exGcd(b,a%b,x,y); LL t = x; x = y; y = t - a/b * y; return r; } LL exCrt() { LL x,y; LL M = bi[1], ans = ai[1];// 特判 第一组 解 for(int i=2;i<=n;i++) { LL a = M, b = bi[i], c = (ai[i] - ans % b + b) % b;// a*x ≡ c (mod b) LL gcd = exGcd(a,b,x,y); x = quickmul(x,c/gcd,b/gcd);// 相当于 x *= c/gcd ans += x * M; M *= (b / gcd); ans = (ans % M + M) % M;// 找最小的ans } return (ans % M + M) % M; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&bi[i],&ai[i]); printf("%lld",exCrt()); return 0; }