中国剩余定理
中国剩余定理
前置知识
逆元(扩展欧几里得算法,费马小定理),模意义下的四则运算
介绍
中国剩余定理主要用于解决这样的线性同余方程组,其中 \(b_1,b_2,\cdots,b_n\) 两两互质
对于这样的方程组我们只需要求出 \(x\) 的一组特解,把这个特解加上 \(b_1,b_2,\cdots,b_n\) 的最小公倍数的任意整数倍就可以得到所有解,所以我们就只需要考虑怎么构造出一组特解
例子
下面举个例子来帮助理解
直接构造不太行,但是对于每一个线性同余方程构造出一个特解是非常轻松的,所以我们先设第 \(i\) 个式子的解是 \(y_i\),那么有
最终的 \(x\) 一定是 \(y_i\) 以各种方式组合起来,那么问题就是怎么组合才能得到 \(x\),如果 \(y_i\) 之间相乘就把问题变得更复杂了,所以我们把它们加起来,想办法凑出 \(x\)
如果 \(x=y_1+y_2+y_3\),那么每一个 \(y\) 需要满足什么条件呢?
因为 \(y_1+y_2+y_3\equiv y_1\equiv 2\pmod 3\),所以 \(y_2+y_3\) 一定是 \(3\) 的倍数,当 \(y_2,y_3\) 分别都是 \(3\) 的倍数时这一定也是满足条件的一个特解
所以我们不妨假设 \(y_2,y_3\) 都是 \(3\) 的倍数
同理 \(y_1,y_3\) 都是 \(7\) 的倍数,\(y_1,y_2\) 都是 \(8\) 的倍数
那我们就可以把 \(y_1\) 写成 \(56z_1\),把 \(y_2\) 写成 \(24z_2\),把 \(y_3\) 写成 \(21z_3\)
原方程就变成了这样
然后就非常好求了,只需要求出
也就是 \(21,24,56\) 分别对于 \(3,7,8\) 的乘法逆元,然后在等式两边同时分别乘以 \(2,4,5\) 就可以得到 \(z_1,z_2,z_3\) 的一组特解
用扩展欧几里得算法求出 \(w_1=2,w_2=5,w_3=5\),所以 \(z_1=2w_1=4,z_2=4w_2=20,z_3=5w_3=25\)
于是 \(y_1=56z_1=224,y_2=24z_2=480,y_3=21z_3=525\)
最后就得到 \(x=y_1+y_2+y_3=1229\),由于 \(3,7,8\) 的最小公倍数是 \(168\),我们将 \(1229\) 对于 \(168\) 取模就能得到 \(x\) 的最小正整数解 \(53\)
\(x\) 的通解就能表示为 \(53+168k,k\in\mathbb{Z}\)
数学语言
上面的例子比较好理解,下面用更严谨的数学语言再来描述一遍,没兴趣可以直接跳过
直接构造不太行,但是对于每一个线性同余方程构造出一个特解是非常轻松的,所以我们先设第 \(i\) 个式子的解是 \(y_i\),那么有
最终的 \(x\) 一定是 \(y_i\) 以各种方式组合起来,如果 \(y_i\) 之间相乘就把问题变得更复杂了,所以我们把它们加起来以凑出 \(x\)
如果 \(x=\sum_{i=1}^ny_i\),那么考虑每一个 \(y\) 需要满足的条件
由于 \(y_t\equiv a_t\pmod {b_t}\) 且 \(x=\sum_{i=1}^ny_i\equiv a_t\pmod {b_t}\),所以 \(y_t\equiv\sum_{i=1}^ny_i\pmod {b_t}\)
因为只要构造一组特解所以我们不妨假设 \(\sum_{i=1}^ny_i[i\ne t]\equiv 0\pmod {b_t}\),也就是 \(b|\sum_{i=1}^ny_i[i\ne t]\)
用人话说就是 \(y_t\) 在加上所有其他 \(y\) 之后对于 \(b_t\) 取模的结果仍然不变,所以其他所有 \(y\) 的和一定是 \(b_t\) 的倍数,因为只要构造一组特解所以我们不妨假设其他每一个 \(y\) 都是 \(b_t\) 的倍数
不妨令 \(p_t=\prod_{i=1}^nb_i[i\ne t]\) 设 \(y_t=p_tz_t\)
原方程组就转换为了
假设 \(p_t\) 在模 \(b_t\) 意义下的乘法逆元为 \(w_t\),那么根据乘法逆元的定义 \(p_tw_t\equiv1\pmod {b_t}\),两边同时乘以 \(a_t\) 就有 \(p_ta_tw_t\equiv a_t\pmod {b_t}\),所以 \(a_tw_t\) 一定是 \(z_t\) 的一个特解
那么对于 \(p_tz_t\equiv a_t\pmod {b_t}\),\(z_t\) 一定有一个特解为 \(p_t\) 在模 \(b_t\) 意义下的乘法逆元的 \(a_t\) 倍
\(p\) 可以直接算出来,然后利用扩展欧几里得算法(\(b\) 为质数时也可以使用费马小定理)求出 \(w\),乘上 \(a\) 就能得到 \(z\),再将 \(z\) 乘上 \(p\) 就能得到 \(y\),全部加起来就可以算出 \(x\) 的一个特解了,只需要模 \(b_1,b_2,\cdots,b_n\) 的最小公倍数就可以得到最小正解
C++ Code
#include<bits/stdc++.h>
#define in read()
using namespace std;
typedef long long ll;
inline int read()
{
char c=getchar();
int x=0;
while(c<48)c=getchar();
while(c>47)x=(x*10)+(c^48),c=getchar();
return x;
}
inline void mwrite(ll a)
{
if(a>9)mwrite(a/10);
putchar((a%10)|48);
}
inline void write(ll a,char c)
{
mwrite(a);
putchar(c);
}
//上面是IO优化
const int MAXN=15;
int n,c[MAXN],d[MAXN];
inline void exgcd(ll a,ll b,ll &x,ll &y)//扩展欧几里得
{
if(!b) return x=1,y=0,void(0);
exgcd(b,a%b,y,x);
y-=(a/b)*x;
}
inline ll inv(ll x,ll p)//求逆元
{
ll X,Y;
exgcd(x,p,X,Y);
return (X%p+p)%p;
}
inline ll crt()//CRT
{
ll prod=1,ans=0;
for(int i=1;i<=n;++i) prod*=d[i];
ll tmp;
for(int i=1;i<=n;++i)
{
tmp=prod/d[i];
ans+=(inv(tmp,d[i])*c[i]*tmp)%prod;
}
return ans%prod;
}
signed main()
{
n=in;
for(int i=1;i<=n;++i) d[i]=in,c[i]=in;
write(crt(),'\n');
return 0;
}