CRT和EXCRT
CRT
简介
CRT(中国剩余定理)可用于求解如下形式的线性同余方程组
其中 \(a_1,a_2,\cdots,a_n\) 两两互质
流程
计算所有模数的积 \(M = \prod_{i=1}^n a_i\)
对于第 \(i\) 个方程,计算 \(m_i=\frac{M}{a_i}\)
由于模数两两互质,所以 \(m_i\) 在模 \(a_i\) 的意义下存在逆元
计算 \(m_i\) 在模 \(a_i\) 意义下的逆元 \(m_i^{-1}\) ,计算 \(c_i=m_i\cdot m_i^{-1}\)
可得方程的一个解 \(x_0\) 为:
方程的通解 \(x\) 为:
方程的最小正整数解 \(x_1\) 为:
时间复杂度 \(O(n\log n)\)
证明
我们需要验证, \(\forall i,x_0 \equiv b_i\pmod{a_i}\)
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
int a[15], b[15];
ll exgcd(ll a, ll b, ll & x, ll & y)
{
if(b == 0) {
x = 1;
y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
ll inverse(ll a, ll b)
{
ll x, y;
exgcd(a, b, x, y);
return (x % b + b) % b;
}
ll CRT()
{
ll M = 1, ans = 0;
for(int i = 1; i <= n; i++)
M *= a[i];
for(int i = 1; i <= n; i++) {
ll m = M / a[i];
ans = (ans + b[i] * inverse(m, a[i]) % M * m % M) % M;
}
return ans;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d%d", &a[i], &b[i]);
}
printf("%lld\n", CRT());
return 0;
}
EXCRT
简介
CRT可求解模数两两互质的同余方程组,而失去模数两两互质这一条件后,就无法求出逆元,从而无法像上面那样构造出符合条件的解,EXCRT可以解决这一问题
原理
我们先考虑含有两个同余方程的方程组如何求解:
可以把条件写成:
移项得:
这样就变成了一个新的同余方程,由裴蜀定理可知 \(\gcd(a_1,a_2)\not\mid b_2-b_1\) 时无解,若解存在,可以用EXGCD求解
设 \(k_1=k\) 为 \(k_1a_1-k_2a_2=\gcd(a_1,a_2)\) 的一个解,则原方程的一个解为:
带回条件得:
所以原先的两个方程可合并为:
这样就得到了方程组的解,对于包含更多同余方程的方程组,按照这样的方法逐个合并到只剩最后一个即可
时间复杂度 \(O(n\log n)\)
代码
由于本题数据大小超过了 long long
的范围,所以用 __int128
,而 __int128
无法用 cout\cin
进行输入输出,所以先用 long long
再强制转换,而运算过程中全部使用 __int128
#include<bits/stdc++.h>
#define ll __int128
using namespace std;
int n;
ll a1, b1, a2, b2;
ll exgcd(ll a, ll b, ll & x, ll & y)
{
if(b == 0) {
x = 1;
y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
void merge()
{
ll k, y, d;
d = exgcd(a1, a2, k, y);
ll t = a2 / d;
k = (k % t + t) % t;
ll mod = a1 / d * a2;
b1 = ((b1 + a1 / d * k * (b2 - b1)) % mod + mod) % mod;
a1 = mod;
}
int main()
{
scanf("%d", &n);
long long a, b;
scanf("%lld%lld", &a, &b);
a1 = a, b1 = b;
for(int i = 1; i < n; i++) {
scanf("%lld%lld", &a, &b);
a2 = a, b2 = b;
merge();
}
printf("%lld\n", (long long)(b1 % a1));
return 0;
}