线性同余方程+中国剩余定理

发布时间 2023-08-27 16:41:52作者: Ian8877

逆元

求解\(ax=b\pmod m\),其实等价于\(ax+my=b\),然后扩欧就无了。

可以应用于求当是\(a,p\)互质,求\(a\)在模\(p\)意义下的逆元,方法就是求解\(ax=1\pmod p\)

中国剩余定理(CRT)

问题:

\(m_1,m_2,...,m_n\)\(n\)个整数两两互质,还给定\(a_1,a_2,...,a_n\),需要我们求解一个方程组:

\( \begin{cases} x=a_1\pmod {m_1}\\ x=a_2\pmod {m_2}\\ ...\\ x=a_n\pmod {m_n} \end{cases} \)

定理

\(m=\prod\limits_{i=1}^n m_i\)(全部\(m_i\)的积),\(M_i=m/m_i\)(除本身外所有\(m_i\)的积),以及\(t_i\)为方程\(M_it_i=1\pmod {m_i}\)\(M_i\)在模\(m_i\)意义下的逆元)。

则整数解\(x=\sum\limits_{i=1}^na_iM_it_i\)

证明

对于一个方程编号为\(i\),我们可以发现此时对于所有的\(k \in [1,n]\)\(k!=i\),由\(M_i\)的定义会有\(a_iM_it_i=0\pmod{m_k}\),又由\(t_i\)的定义会有\(a_iM_it_i=a_i\pmod {m_i}\),则\(x=\sum\limits_{i=1}^na_iM_it_i\)带入原方程组显然成立。

通解

对于一个整数k,由于\(m\)的定义,显然会有\(x+km\)对原方程组仍然成立。

实现:P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪

#include<algorithm>
#include<stdio.h>
#include<queue>
#define ll long long
using namespace std;
int n;
int a[15],b[15];
ll M[15],t[15];
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),z=x-a/b*y;
	x=y,y=z;
	return d;
}
int main() {
//	freopen("P1495.in","r",stdin);
//	freopen("P1495.out","w",stdout);
	ll m=1;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d%d",&a[i],&b[i]);
		m*=a[i];
	}	
	ll sum=0;
	for(int i=1;i<=n;i++) {
		M[i]=m/a[i];
		ll y,d=exgcd(M[i],a[i],t[i],y);
		sum=(sum+1ll*b[i]*M[i]*t[i]%m+m)%m;
	}
	printf("%lld",sum);
	return 0;
}

扩展中剩(EXCRT)

虽然说是拓展,但好像与中剩没什么关系。

问题

我们注意到,上面中剩虽然好用,但是局限性太大了,要求所有的模数都互质,我们更普遍见到模数不互质的情况,此时就可以引出扩展中剩:一种通过合并方程的求解同余方程方法。

推导

我们接下来就来探讨该如何对方程进行合并,假如给出两个方程:
\(\begin{cases} x=a_1\pmod {m_1}\\ x=a_2\pmod {m_2} \end{cases}\)

我们先将方程写成等式形式:
\(\begin{cases} x=a_1+k_1m_1\\ x=a_2+k_2m_2 \end{cases}\)

联立得:\(a_1+k_1m_1=a_2+k_2m_2\).

移项得:\(k_1m_1=(a_2-a_1)+k_2m_2\).

接下来设\(d=\gcd(m_1,m_2)\),将原式除以\(d\),便得:\(k_1\frac {m_1} d=\frac {a_2-a_1} d+k_2\frac {m_2} d\).

我们假设以\(\frac {m_2} d\)为一个模数,就会得到:\(k_1\frac {m_1} d=\frac {a_2-a_1} d\pmod {\frac {m_2} d}\),发现我们将\(k_2\)消去了。

接下来将两边都除去\(\frac {m_1} d\),得到:\(k_1=inv(\frac {m_1} d,\frac {m_2} d) \frac {a_2-a_1} d+\frac {m_2}dy\),其中\(inv(\frac {m_1} d,\frac {m_2} d)\)表示\(\frac {m_1} d\)在模\(\frac {m_2} d\)意义下的逆元,因为两数显然互质,所以有逆元,此式中我们将同余方程形式转换成了等式,便于后面操作。

然后我们可以将\(k1\)回带到最开始的式子\(x=a_1+k_1m_1\)中,得到了\(x=inv(\frac {m_1} d,\frac {m_2} d) \frac {a_2-a_1} dm_1+\frac {m_1m_2}dy+a_1\).

发现我们为了将未知的\(y\)消去,可以设定模数为\(\frac {m_2} d\),于是我们便得到了一个新的同余方程:\(x=inv(\frac {m_1} d,\frac {m_2} d) \frac {a_2-a_1} dm_1+a_1\pmod {\frac {m_1m_2} d}\),方程中的一切量都是我们已知的,自此,我们成功将两个同余方程合并了一个新的同余方程,达到了我们的目的。

注意,方程中出现了\(\frac {a_2-a_1} d\)这一个分数,只有当\(d|a_2-a_1\)时使得方程有解,否则方程无解,这需要在一开始就判断。

以此类推,最后会只剩下一个方程\(x=a\pmod m\),此时的余数\(a\)就是\(x\)的最小正整数取值。

实现

有几个小点需要注意:

1.逆元的求解可以使用扩欧,但要记得最后最好将其转化为非负数。

2.求出\(inv(\frac {m_1} d,\frac {m_2} d) \frac {a_2-a_1} d\)后我们可以将其对\(\frac {m_2} d\)取模,毕竟它本来就是在对\(\frac {m_2} d\)取模意义下的。

3.记得将每次合并方程后求出的\(a\)\(m\)取模然后转为非负。

4.乘数较为频繁,模次数较少,当数据太强时记得使用int128增强范围。

代码:P4777 【模板】扩展中国剩余定理(EXCRT)

#include<algorithm>
#include<stdio.h>
#include<queue>
#define ll long long
#define maxn 100005
using namespace std;
int n;
ll read() {
	ll k=0; char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0' && c<='9') k=k*10+c-'0',c=getchar();
	return k;
}
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),z=x-a/b*y;
	x=y,y=z;
	return d;
}
ll inv(ll a,ll b) {
	ll x,y,d=exgcd(a,b,x,y);
	x=(x%b+b)%b;
	return x;
}
ll m[maxn],r[maxn];
int main() {
//	freopen("P4777.in","r",stdin);
//	freopen("P4777.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) {
		m[i]=read(),r[i]=read();
	}
	for(int i=1;i<n;i++) {
		ll m1=m[i],m2=m[i+1],r1=r[i],r2=r[i+1];
//		printf("%lld %lld %lld %lld ",m1,r1,m2,r2);
		ll x,y,d=exgcd(m1,m2,x,y);
		m[i+1]=(m1/d)*(m2/d)*d;
		r[i+1]=(__int128)inv(m1/d,m2/d)*((r2-r1)/d)%(m2/d)*m1+r1;
		r[i+1]=(r[i+1]%m[i+1]+m[i+1])%m[i+1];
//		printf("%lld %lld\n",m[i+1],r[i+1]);
	}
	printf("%lld",r[n]);
	
	return 0;
}

THE END