# 中国剩余定理 (CRT)
中国剩余定理是用来解决如下问题的:
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧x≡a1 (modm1)x≡a2 (modm2)... x≡an (modmn)
注意:模数必须互质。
# 解法
我们设
M=i=1∏nmiMi=miMMiti≡1(modmi)
ti 相当于是 Mi 在模 mi 意义下的逆元。
此时我们可以构造出一组解:
x=i=1∑naiMiti
进而推出任意解:
x0=x+k×M
# 证明
根据数组的含义,容易得到,对于第 i 个同余方程,当 j=i 时,都有:
ajMjtj≡0(modmi)
因为 Mj 中包含 mi。
当 j=i 时,有:
j=1∑najMjtj≡ai(modmi)
证毕.
逆元需要用到扩展欧几里得算法 (exgcd) 来求解,这里不再赘述,不会的左转百度一下吧 QwQ
# 代码
例题:P1495 【模板】中国剩余定理 (CRT)/ 曹冲养猪
| #include <bits/stdc++.h> |
| |
| #define ll long long |
| |
| using namespace std; |
| |
| ll n, mul = 1, ans; |
| ll a[15], b[15], m[15]; |
| |
| inline void exgcd(ll a, ll b, ll &x, ll &y){ |
| if(!b) return x = 1, y = 0, void(); |
| exgcd(b, a % b, x, y); |
| ll z = x; |
| x = y, y = z - (a / b) * y; |
| } |
| |
| signed main(){ |
| scanf("%lld", &n); |
| for(ll i = 1; i <= n; i++){ |
| scanf("%lld%lld", &a[i], &b[i]); |
| mul *= a[i]; |
| } |
| for(ll i = 1; i <= n; i++){ |
| m[i] = mul / a[i]; |
| ll x = 0, y = 0; |
| exgcd(m[i], a[i], x, y); |
| if(x < 0) x += a[i]; |
| ans += b[i] * m[i] * x; |
| } |
| printf("%lld\n", ans % mul); |
| return 0; |
| } |
(远古代码,有些丑陋,见谅。)
# 扩展中国剩余定理 (exCRT)
注意到多了扩展,那么这个扩展在哪里呢?
普通的 CRT 要求模数必须互质,这就很恶心,exCRT 就解决了这个问题,也就是模数不互质时的解决方法。
# 解法
同样是这个式子:
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧x≡a1 (modm1)x≡a2 (modm2)... x≡an (modmn)
模数不互质时怎么办呢?
我们考虑能否把两个同余方程合并为一个。
我们对两个方程进行讨论:
{x≡a1 (modm1)x≡a2 (modm2)
同余也可以表示为:
x=k1m1+a1=k2m2+a2
移项:
k1m1−k2m2=a2−a1
诶,这不就是一个 exgcd 的形式吗,所以容易判断是否有解:
- gcd(m1,m2)∣(a2−a1),有解。
- 否则无解。
继续来看这个式子,设:
d=gcd(m1,m2)p1=dm1p2=dm2
显然 p1 和 p2 是互质的,代入到原式中:
k1p1−k2p2=da2−a1
这个式子中,左边满足了 gcd(p1,p2)=1,右边当且仅当 d∣(a2−a1) 才有解,利用 exgcd 求出一组特解:
x1p1+x2p2=1
然后可以直接算出 k1,k2:
{k1=da2−a1x1k2=−da2−a1x2
于是:
x=k1m1+a1=da2−a1x1mi+a1
至此,我们成功构造关于方程
{x≡a1 (modm1)x≡a2 (modm2)
的一个解,但是整个解系如何构造呢?
定理: 我们有一个特解 x0,那么方程
{x≡a1 (modm1)x≡a2 (modm2)
通解为:
x=x0+k×lcm(m1,m2)
即
x≡x0(modlcm(m1,m2))
这个构造正确性是显然的。
那么接下来还有一个结论: 0,1,2⋅⋅⋅lcm(m1,m2) 中只有一个解。
关于这个的证明,只需要设存在 x,y(x,y∈[0,lcm(m1,m2)], x≥y) 都是解。
然后推一下式子即可得出,x,y 都是解当且仅当 x=y。
# 代码
| #include <bits/stdc++.h> |
| #define ll long long |
| #define Int __int128 |
| |
| using namespace std; |
| |
| namespace IO{ |
| inline int read(){ |
| int x = 0; |
| char ch = getchar(); |
| while(!isdigit(ch)) ch = getchar(); |
| while(isdigit(ch)) x = (x << 3)+ (x << 1) + ch - '0', ch = getchar(); |
| return x; |
| } |
| |
| template <typename T> inline void write(T x){ |
| if(x > 9) write(x / 10); |
| putchar(x % 10 + '0'); |
| } |
| } |
| using namespace IO; |
| |
| const Int N = 1e5 + 10; |
| Int a1, b1, a2, b2, flag; |
| ll a[N], b[N], n; |
| |
| inline Int exgcd(Int a, Int b, Int &x, Int &y){ |
| if(!b) return x = 1, y = 0, a; |
| Int g = exgcd(b, a % b, x, y); |
| Int z = x; |
| x = y, y = z - (a / b) * y; |
| return g;e |
| } |
| |
| inline void merge(){ |
| Int d = b2 - b1, x, y, g; |
| g = exgcd(a1, a2, x, y); |
| Int mod = a2 / g; |
| if(d % g == 0){ |
| x = ((x * d / g) % mod + mod) % mod; |
| b1 = x * a1 + b1; |
| a1 = (a1 * a2) / g; |
| }else flag = 1; |
| } |
| |
| inline Int excrt(){ |
| a1 = a[1], b1 = b[1]; |
| for(Int i = 2; i <= n; i++){ |
| a2 = a[i], b2 = b[i]; |
| merge(); |
| if(flag) return -1; |
| } |
| return b1; |
| } |
| |
| signed main(){ |
| n = read(); |
| for(Int i = 1; i <= n; i++) a[i] = read(), b[i] = read(); |
| write(excrt()), puts(""); |
| return 0; |
| } |
# End