中国剩余定理(扩展)

算法简介

中国剩余定理(Chinese Remainder Theorem,CRT)可以用来求解如下形式的一元线性同余方程组。(注意\(n_1,n_2,\cdots,n_k\),两两互质)

\[\left\{ \begin{aligned} x\ &\equiv\ a_1\ (mod\ n_1)\\ x\ &\equiv\ a_2\ (mod\ n_2)\\ x\ &\equiv\ a_3\ (mod\ n_3)\\ &\vdots\\ x\ &\equiv\ a_k\ (mod\ n_k)\\ \end{aligned} \right. \]

基本思路
如果可以找到k个x满足:

\[\left\{ \begin{aligned} x_i\ &\equiv\ a_i\ (mod\ n_i)\\\\ x_i\ &\equiv\ 0\ (mod\ n_j)\ \ i\neq j \end{aligned} \right. \]

那么显然将这k个x加起来就能得到一个满足要求的解。
进一步来看。\(x_i\ \equiv\ 0\ (mod\ n_j)\ \ i\neq j\),这个条件很好满足,只需要令 \(x_i = \frac{n_1n_2\cdots n_k}{n_i}\) 即可,记这个值为 \(m_i\),为了满足 \(x_i\ \equiv\ a_i\ (mod\ n_i)\\\),直接令 \(x_i = a_i\),然而 \(a_im_i\),还不能满足要求,再乘以一个逆元就行啦。

算法流程

1.计算所有模数的积\(N=n_1n_2\cdots n_k\)
2.对于每个\(x_i\)
\(\quad\)a.计算\(m_i=\frac{N}{n_i}\)
\(\quad\)b.计算\(m_i在模n_i意义下的逆元m_i^{-1}\)
3.方程组的解为\(x=\sum_{i=1}^{k}x_i=\sum_{i=1}^{k}a_im_im_i^{-1}(mod\ N)\)

例题
P1495 【模板】中国剩余定理(CRT)/曹冲养猪
参考代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
int n;
ll a[20];
ll N=1;
ll exgcd(ll a,ll b,ll&x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	ll temp;
	ll d=exgcd(b,a%b,x,y);
	temp=x;
	x=y;
	y=temp-a/b*y;
	return d;
}
ll ans=0;
ll b[20];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%lld%lld",&a[i],&b[i]);
		N*=a[i];	
	}
	for(int i=1;i<=n;++i){
		ll x,y;
		ll d=exgcd(N/a[i],a[i],x,y);
		x=(x%a[i]+a[i])%a[i];//注意exgcd求解得到的逆元可能是负的
		ans+=b[i]*N/a[i]*x;
		ans%=N;
	}	
	printf("%lld",ans);
	return 0;
}

扩展

对于刚刚的问题,若模数不严格两两互质,就无法用扩展欧几里得求乘法逆元,所以要使用新的算法。

基本思路
假设我们已经求出前\(k-1\)个方程组的一个解,并且有\(N=LCM_{i=1}^{k-1}n_i\)(及N是前k-1个数的最小公倍数)。
则前k-1个方程的通解为\(x+i*N(i\in Z)\)
那么对于第k个方程,要找到一个合适的i使\(x+i*N\equiv a_k(mod\ n_k)\)
其实就是求解\(i*N\equiv a_k-x(mod\ n_k)\),这个时候就又可以用扩展欧几里得求解了。然后再一直往下合并即可。
例题
P4777 【模板】扩展中国剩余定理(EXCRT)
参考代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10;
ll ai[N],bi[N];
int n;
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,x,y);
	ll temp=x;
	x=y;
	y=temp-a/b*y;
	return d;
}
ll mul(ll a,ll b,ll mod){
	ll ans=0;
	while(b){
		if(b&1)ans=(ans+a)%mod;
		a=(a+a)%mod;
		b>>=1;
	}
	return ans;
}
ll excrt(){
	ll M=ai[1],ans=bi[1];
	for(int i=2;i<=n;++i){
		ll x,y,a=M,b=ai[i];
		ll c=(bi[i]-ans%b+b)%b;
		ll d=exgcd(a,b,x,y);
		c=mul(x,c/d,ai[i]/d);
		ans+=c*M;
		M*=ai[i]/d;
		ans=(ans%M+M)%M;
	}
	return ans;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	scanf("%lld%lld",&ai[i],&bi[i]);
	printf("%lld",excrt());
	return 0;
}
posted @ 2022-04-29 22:23  何太狼  阅读(114)  评论(0编辑  收藏  举报