数论补完计划 Part4 中国剩余定理
中国剩余定理
《孙子算经》有云:
今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?
大意为:有
刚入坑编程的估计在“循环结构”那一章里就写过求解这个方程的枚举代码了。这种方式不好的一点就是码量随方程数的增多而膨胀(但是你可以写一个生成计算代码的程序,然后运行编译后的程序)。我们急需一种通解,在码量不增的情况下实现对这个方程组的求解。
我们考虑拆开这个方程组,分别令
两个方程组的写法其实是等价的,只是把同余转化为了带余除法罢了。我们惊奇地发现,如果再加上几个约束条件,原方程组的解
此时
而我们的中国剩余定理断言道,对于模数均互质的
它的解
int n;ll m[N], a[N];ll M = 1;
ll exgcd(ll a, ll b, ll &x, ll &y) { if (!b) { x = 1, y = 0; return a; } ll d = exgcd(b, a % b, y, x); y -= a / b * x; return d;}
ll inv(ll x, ll p) { ll ans, tmp; exgcd(x, p, ans, tmp); return (ans % p + p) % p;}
ll CRT() { ll res = 0; for (int i = 1; i <= n; i++) M *= m[i]; for (int i = 1; i <= n; i++) { ll Mi = M / m[i]; res += a[i] * Mi * inv(Mi, m[i]); } return res % M;}扩展中国剩余定理
经历过数论大风大浪的应该都知道,马上要开始推导模数不全互质的情况了。
还是那个方程组:
此时不保证
对大量事实的观测表明,两个线性同余方程在合并后遵循如下性质:
- 模数变为原两个方程模数的最小公倍数
- 合并得到的方程与原方程同型
- 合并得到的方程不一定有解
也即:
事实上,假设我们有方程
观察到这是二元一次不定方程
约去最大公倍数得:
于是
那么对于方程
仅当
我们假设
做差可得:
得到关系
总结一下,对于方程组:
有解的充要条件是
#include <bits/stdc++.h>
#define N 100010using namespace std;
typedef long long ll;
struct Equation { ll a, m;};
ll a[N], b[N];stack <Equation> stk;
ll exgcd(ll a, ll b, ll &x, ll &y) { // 扩展欧几里得过程,求解特解 t1 和 t2 if (!b) { x = 1, y = 0; return a; } ll d = exgcd(b, a % b, y, x); y -= a / b * x; return d;}
ll mul(ll a, ll b, ll p) { // 著名的龟速乘,就是把快速幂中的乘号改成加号,用来在不爆范围的情况下计算乘积取模的结果 ll res = 0; bool flag = (b < 0) ^ (a < 0); // 处理负数情况,同号相乘为正,异号为负 a = abs(a) % p; b = abs(b) % p; // 注意到:a * (-b) = -(a * b),数字按绝对值相乘,负号最后处理 while (b) { if (b & 1) res = (res + a) % p; a = (a + a) % p; b >>= 1; } return res * (flag ? -1 : 1) % p; // 处理负号}
ll exCRT() { while (stk.size() > 1) { // 不断合并方程直至剩下最后一个 Equation cur = stk.top(); stk.pop(); Equation nxt = stk.top(); stk.pop();
ll t1, t2; ll D = gcd(cur.m, nxt.m); ll S = lcm(cur.m, nxt.m); if ((nxt.a - cur.a) % D != 0) return -1; // 无解判断 exgcd(cur.m, nxt.m, t1, t2); // 求解特解 ll X = ((mul(mul(cur.m, t1, S), (nxt.a - cur.a) / D, S)) + cur.a) % S; stk.push((Equation) {X, S}); } Equation res = stk.top(); return (res.a % res.m + res.m) % res.m; // 转为正整数}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i] >> b[i]; for (int i = 1; i <= n; i++) stk.push((Equation) {b[i], a[i]}); cout << exCRT() << endl;
return 0;}支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
部分内容可能已过时