中国剩余定理及扩展

《孙子算经》中有这么个问题:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?我们可以用如下的同余方程组表示这个问题:

\tiny \left\{ \begin{aligned} x = 2 (mod\: 3) \\ x = 3 (mod\: 5) \\ x = 2 (mod\: 7) \\ \end{aligned} \right.

显然我们直接找这个x是十分困难的,但是我们可以很容易找到\tiny n_{1},n_{2},n_3,使得他们满足:

\tiny \left\{ \begin{aligned} p_1 = 2 (mod\: 3) \\ p_2 = 3 (mod\: 5) \\ p_3 = 2 (mod\: 7) \\ \end{aligned} \right.

那么我们看看\tiny x=p_1+p_2+p_3是不是有可能满足上一个不等式呢?显然有点憨啊2333。不过呢,我们来一个式子一个式子地看,当我们要求\tiny x=2(mod:\ 3)时,我们可以推出\tiny p_2=0(mod:\ 3)并且\tiny p_3=0(mod:\3)。我们在考察了三个式子之后,发现实际上,我们会得到要让\tiny x=p_1+p_2+p_3成为答案,除了满足上述不等式之外,还需要让\tiny p_1是5的倍数和7的倍数,而由于5和7互质,因此要求\tiny p_1是35的倍数,要求\tiny p_2是21的倍数,要求\tiny p_3​是15的倍数。​​​​​​于是我们将\tiny p_i重新表示为如下:

\tiny \left\{ \begin{aligned} p_1 = 35*q_1 \\ p_2 = 21*q_2 \\ p_3 = 15*q_3 \\ \end{aligned} \right.

带入上述同余方程后我们得到:

\tiny \left\{ \begin{aligned} 35*q_1 = 2 (mod\: 3) \\ 21*q_2 = 3 (mod\: 5) \\ 15*q_3 = 2 (mod\: 7) \\ \end{aligned} \right.

由于\tiny gcd(35,3)=gcd(21,5)=gcd(15,2)=1,一次上述三个方程必定有解,并且可以通过扩展欧几里得的方式求出。我们通过扩展欧几里得解如下三个方程:

\tiny \left\{ \begin{aligned} 35*w_1 = 1 (mod\: 3) \\ 21*w_2 = 1 (mod\: 5) \\ 15*w_3 = 1 (mod\: 7) \\ \end{aligned} \right.

有没有发现这边就是在求解逆元。并且由于模数之间两两互质,我们知道,\tiny w_i的系数比与所在方程的模数互质(如35和3),于是在这种条件下,我们有很多方法可以使用,最简单的莫过于费马小定理(注意数据溢出)。

并且有如下关系:

\tiny \left\{ \begin{aligned} q_1=2*w_1 \\ q_2=3*w_2 \\ q_3=2*w_3 \\ \end{aligned} \right.

解得\tiny w_1=2,w_2=1,w_3=1,进一步,我们得到

\tiny \left\{ \begin{aligned} q_1=2*w_1=4 \\ q_2=3*w_2=3 \\ q_3=2*w_3=2 \\ \end{aligned} \right.

于是我们可以解出\tiny x=4*35+3*21+2*15=140+63+30=233。由于3、5、7互质,因此,任何一个特解x加上或者减去3*5*7(最大公倍数)均不会影响答案。

中国剩余定理:

现在我们抽象化上述过程:

设有一组待解方程为:

\tiny \left\{ \begin{aligned} x=a_1 (mod\: m_1) \\ x=a_2 (mod\: m_2) \\ ............... ... \\ x=a_n (mod\: m_n) \\ \end{aligned} \right.

为了构造出答案,我们要求:

\tiny \left\{ \begin{aligned} p_1 = a_1 (mod\: m_1) \\ p_2 = a_2 (mod\: m_2) \\ ............\\ p_n = a_n (mod\: m_n) \\ \end{aligned} \right.

\tiny \bg_white \tiny p=\prod_{i=1}^{n}m_i(即为所有模数的乘积),为了使得我们构造出答案\tiny x=\sum_{i=1}^{n}p_i,因此需要满足倍数性质,于是方程可以转换为求解:

\tiny \left\{ \begin{aligned} \frac{p}{m_1}*q_1 = a_1 (mod\: m_1) \\ \frac{p}{m_2}*q_2 = a_2 (mod\: m_2) \\............... ... \\ \frac{p}{m_n}*q_n = a_n (mod\: m_n) \\ \end{aligned} \right.

由于\tiny \frac{p}{m_i}\tiny m_i互质,因此上述的所有式子均有解,于是我们可以解如下方程组

\tiny \left\{ \begin{aligned} \frac{p}{m_1}*w_1 = 1 (mod\: m_1) \\ \frac{p}{m_2}*w_2 = 1 (mod\: m_2) \\............... ... \\ \frac{p}{m_n}*w_n = 1 (mod\: m_n) \\ \end{aligned} \right.                     \tiny \dpi{200} \tiny \left\{ \begin{aligned} q_1=a_1*w_1 \\ q_2=a_2*w_2 \\ ............... ... \\ q_n=a_n*w_n \\ \end{aligned} \right.

于是我们构造出来的一个特解就可以表示为\tiny x=\sum_{i=1}^{n}p_i=\sum_{i=1}^{n}\frac{p}{m_i}*q_i=\sum_{i=1}^{n}\frac{p}{m_i}*a_i*w_i,由于模数两两互质,因此对与x加上或者减去p都不影响方程结果。

来看一道例题吧~

洛谷 P1495 【模板】中国剩余定理(CRT)/曹冲养猪

提交10.44k

通过4.31k

时间限制1.00s

内存限制125.00MB

题目描述

自从曹冲搞定了大象以后,曹操就开始捉摸让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲满不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。举个例子,假如有 16 头母猪,如果建了 3 个猪圈,剩下 1 头猪就没有地方安家了。如果建造了 5 个猪圈,但是仍然有 1 头猪没有地方去,然后如果建造了 7 个猪圈,还有 2 头没有地方去。你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?

输入格式

第一行包含一个整数 n—建立猪圈的次数,接下来 n 行,每行两个整数 a_i,b_i​, 表示建立了 a_i 个猪圈,有 b_i头猪没有去处。你可以假定 a_i​,a_j互质。

输出格式

输出包含一个正整数,即为曹冲至少养母猪的数目。

输入输出样例

输入 #1复制

3
3 1
5 1
7 2

输出 #1复制

16

思路:本题的数据范围很大,使用费马小定理求逆元可能需要处理高精度,因此我们这里选择使用扩展欧几里得。

 

#include<bits/stdc++.h>
using namespace std;
const int N = 15;

typedef long long ll;

ll m[N],a[N];

// gcd(a,b) = gcd(b,a%b)
// a * x1 + b * y1 = b * x + ( a - a/b*b ) * y
// a * x1 + b * y1 = a * y + ( x - a/b*y ) b
// x1 = y , y1 = x - a/b*y

ll extgcd(ll a,ll b,ll &x,ll &y){
	if( ! b ){
		x = 1;
		y = 0;
		return a;
	}
	ll temp = extgcd(b,a%b,x,y);
	ll x1,y1;
	x1 = y;
	y1 = x - a/b*y;
	x = x1;
	y = y1;
	return temp;	
}

ll inv(ll a,ll b){ // 求a在模b意义下得逆元
	ll x,y,gcd; //本题中 a b 必互质 但是为了算法的通用性 此处给出最小整数解的公式
	gcd = extgcd(a,b,x,y); 
	b /= gcd;
	return ( (x % b) + b ) % b;
}

ll CRT(int n){
	ll p = 1, x = 0, invQ;
	int i;
	for( i = 1;i <= n; ++ i){
		p *= m[i];
	}
	for( i = 1;i <= n; ++ i){
		invQ = p / m[i];
		x += ( invQ * a[i] * inv( invQ, m[i]) ) % p;
	}
	return x % p;
}

int main(){
	int i, n;
	ll x, p;
	cin >> n;
	for( i = 1;i <= n; ++ i){
		cin >> m[i] >> a[i];
	}
	cout<< CRT(n);
	return 0;
} 

 

扩展中国剩余定理

当然在实际问题中,我们可能遇到的是模不为质数,甚至都不满足互质的情形,此时就需要请出我们的扩展中国剩余定理了。不过扩展中国剩余定理和中国剩余定理没有半毛钱关系=w=,而是通过n次扩展欧几里得求解答案:

有一组待解同余方程组为:

\tiny \left\{ \begin{aligned} x=a_1 (mod \: m_1) \\ x=a_2 (mod\: m_2) \\ ............... ... \\ x=a_n (mod\: m_n) \\ \end{aligned} \right.

我们假设在考虑了前i-1个同余方程后,得到的特解为\tiny x,且设\tiny \bg_white \tiny M=\prod_{j=1}^{i-1}m_j,那么通解可以表示为x + k*M,其中M为正整数,此时我们将通解带入第i个方程,就可以得到x+k*M = a_{i}(mod \ m_{i}),移项得\tiny k*M=a_i-x(mod\ m_i),这个方程的有解条件为\tiny gcd(M,m_i)\ |\ (a_i-x)。通过扩展欧几里得我们将同余方程不断合并,得到新的通解表达式,直到处理完所有方程,算法结束。

在初始化的时候,我们就可以使\bg_white x=a_1,M=m_1,然后逐步考虑每一个方程。

洛谷 P4777 【模板】扩展中国剩余定理(EXCRT)

提交15.96k

通过5.62k

时间限制1.00s

内存限制500.00MB

题目描述

给定 nn 组非负整数 a_i, b_iai​,bi​ ,求解关于 xx 的方程组的最小非负整数解。

\begin{cases} x \equiv b_1\ ({\rm mod}\ a_1) \\ x\equiv b_2\ ({\rm mod}\ a_2) \\ ... \\ x \equiv b_n\ ({\rm mod}\ a_n)\end{cases}⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧​x≡b1​ (mod a1​)x≡b2​ (mod a2​)...x≡bn​ (mod an​)​

输入格式

输入第一行包含整数 nn。

接下来 nn 行,每行两个非负整数 a_i, b_iai​,bi​。

输出格式

输出一行,为满足条件的最小非负整数 xx。

输入输出样例

输入 #1复制

3
11 6
25 9
33 17

输出 #1复制

809

思路:模板题,需要注意的是本题数据可能会在乘法的过程中爆long long 型的精度范围,因此可以写一个龟速乘(快速幂变快速乘,一样的二进制分解思想)。同时注意输入的数据也会超过int的数据类型,使用快读需注意。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MaxN = 100010;

ll a[MaxN],m[MaxN];

inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}

ll quickMul(ll a,ll b,ll p){
	ll res = 0;
	while( b ){
		if(b & 1){
			res = (res + a) % p;
		}
		a = ( a + a ) % p;
		b >>= 1;
	}
	return res;
}

ll extgcd(ll a,ll b,ll &x,ll &y){
	if ( ! b ){
		x = 1;
		y = 0;
		return a;
	}
	ll temp = extgcd(b,a%b,x,y);
	ll x1,y1;
	x1 = y;
	y1 = x - a/b * y;
	x = x1;
	y = y1;
	return temp;
}



ll solve(int n){
	int i;
	ll M = m[0],x = a[0],c,gcd,t,y;
	for(i = 1; i < n; ++ i){
		c = (( a[i] - x ) % m[i] + m[i] ) %m[i] ;//右边的数 通过取模 对齐到正数 
		gcd = extgcd(M, m[i], t, y);
		if( c % gcd != 0) return -1; //无解
		m[i] /= gcd;
		//	t = ( (t % m[i] + m[i]) % m[i] ) * (c / gcd);// 求t的最小正正数解 
		t = quickMul(t, c / gcd, m[i]);
		x += t * M;
		M *= m[i] ;
		x = (x % M + M) % M; 
	}
	return (x % M + M) % M;
}

int main(){
	int n, i;
	n = read();
	for( i = 0; i < n ; ++ i){
		scanf("%lld%lld",m+i,a+i);
	} 
	printf("%lld\n",solve(n));
	return 0;
}
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值