CRT,中国剩余定理
介绍
中国剩余定理 (Chinese remainder theorem, CRT),简单而言,给定一个未知数 和 个关于 的线性同余方程 (Congruence Equation) 组成的线性同余方程组 (Congruence Equation System),其中任意两模数互质,求满足该方程组的最小非负整数解。
形如:
的方程组称为线性同余方程组。
其中 为同余符号。如果给定一个正整数 (即模数),如果两个整数 和 满足 能被 整除,即 ,那么就称整数 与 对模 同余,记作 。
而 P1495则是 CRT 的模板题,题面较长就不放出来了,但本质就是上面的问题。
原理
原理这种东西,又怎么扯得清楚呢?
我们知道,在 CRT 中,有一个特别的限制:其中任意两模数互质,我们对问题的求解就是需要这个条件。(不信的看看扩中吧)
我们先就 P1495 给的样例来看吧,简单来说,这个问题就是在问:
找到一个最小正整数 ,使得:
以上方程组也可以转化为同余方程组的形式,不过写成这样可能好理解一些。
一下子看三个方程有一点晕吧?没关系,我们试着化大为小:
尝试找出这样的 ,分别使得:
不难发现,。
当然,我们不妨再进行问题的分解。
对于 ,我们不难发现它复杂在于不像 的方程那样,余数为 。那既然这样,我们就让它余数为 !
于是转化为:
只需求出 ,再乘二即可。
那么怎么求 呢?对于 ,因为它一定是 和 的倍数,故设 ,那么就有:
这里要求 的话需要对乘法逆元有所掌握,那么让我手算就有一点刁难我了,所以待会儿我直接用 exgcd 来解这个同余方程,详见下文!
当然当然,对于最后的结果需要为最小正整数解,只需要求:
为模数之积(其实为最小公倍数)。
大家可以自己手算一下。
推导到一般求法就很简单了,还不懂的可以对照下面的 “过程” 再看看。
过程
求模数的最小公倍数 ,由其中任意两模数互质得:
设数 。
找到一个最小正整数数 使得:
则:
注意 3. 应如何找出满足条件的 ,如果直接由小到大枚举肯定时间复杂度过大,此时应观察该方程的形式,发现其可以写成:
的形式。熟悉的同学可以看出来:这不就是求乘法逆元吗?~~ 好吧,其实熟悉的同学在第三步就直接在求乘法逆元了。~~ 对于求乘法逆元,这里不再详细讲解,详见这篇文章。我们这里使用扩展欧几里得算法 (Extended Euclidean algorithm,ExGcd),算法如下:
上面的方程可以写成:
其中 为常数, 就是需要求的未知数, 为辅助解。
对于求解以上不定方程的代码模板如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/*求同余方程ax≡1(\b\bmod b)的最小正整数解。*/
using namespace std;
typedef long long ll;
int x,y;//存储解
void exgcd(int a,int b){
if(b==0){
x=1,y=0;
return;
}//b=0时,方程有一组特解x=1,y=0,作为递归边界
exgcd(b,a%b);//递归
int tmp=x;
x=y;
y=tmp-(a/b)*y;
return;
}//辗转相除,与gcd写法类似
int main()
{
int a,b;
cin>>a>>b;
exgcd(a,b);
cout<<(x+b)%b;//这里不排除为负数解或非最小正整数解
}代码实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using namespace std;
typedef long long ll;
ll x,y;//存储解
void exgcd(ll a,ll b){
if(b==0){
x=1,y=0;
return;
}//b=0时,方程有一组特解x=1,y=0,作为递归边界
exgcd(b,a%b);//递归
ll tmp=x;
x=y;
y=tmp-(a/b)*y;
return;
}//辗转相除,与gcd写法类似
int main()
{
ll n;
cin>>n;
ll a[n],b[n];
ll M=1;
for(ll i=0;i<n;i++){
cin>>a[i]>>b[i];
M*=a[i];//求M
}//输入不解释
ll k[n];
ll ans=0;
for(ll i=0;i<n;i++) k[i]=M/a[i];//求ki
for(ll i=0;i<n;i++){
exgcd(k[i],a[i]);//扩欧解方程
x=(x+a[i])%a[i];//这里不排除为负数解或非最小正整数解
ans+=x*k[i]*b[i]%M;//求ans
}
cout<<ans%M;
}