线性同余方程+中国剩余定理
逆元
求解\(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增强范围。
#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;
}