扩展中国剩余定理学习笔记+模板(洛谷P4777)

题目链接:

洛谷

题目大意:求同余方程组

$xequiv b_i(mod a_i)$

的最小正整数解。

$1leq nleq 10^5,1leq a_ileq 10^{12},0leq b_ileq 10^{12},b_i<a_i$,保证有解,答案不超过 $10^{18}$。

(其实我没打成方程组形式是因为我 $latex$ 太差)


既然是模板就直接讲方法。假设不一定有解。

方法:每次将前 $i-1$ 个方程合并后的方程与第 $i$ 个方程合并,直到 $n$ 个方程全部合并完。

(合并之后的方程也是 $xequiv B(mod A)$ 的形式)

来看看假设前 $i-1$ 个方程合并后是 $xequiv B(mod A)$,第 $i$ 个方程是 $xequiv b_i(mod a_i)$。

那么合并后的新方程模数肯定是 $operatorname{lcm}(A,a_i)$。

发现 $x$ 可以表示成 $kA+B$ 的形式,我们就是要找到一个 $k'$ 使得 $k'A+Bequiv b_i(mod a_i)$,也就是 $k'Aequiv b_i-B(mod a_i)$。

其中 $A,b_i-B,a_i$ 都是已知的,这不就 $EXGCD$ 的模板了吗!

好的。求出 $k'$ 合并之后就是 $xequiv k'A+B(mod operatorname{lcm}(A,a_i))$。如果方程无解(即没有符合条件的 $k'$)那么整个方程组无解。

一路滚下去,最后滚出的 $B$ 就是答案。

时间复杂度:$n$ 次合并,每次一个 $EXGCD$,复杂度 $O(nlog(max{a_i}))$。


代码:(附带判断)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 int n;
 5 ll a[100010],b[100010];
 6 ll qmul(ll a,ll b,ll mod){    //模数是long long范围,要写慢速乘
 7     ll ans=0;
 8     for(;b;b>>=1,a=(a<<1)%mod) if(b&1) ans=(ans+a)%mod;
 9     return ans;
10 }
11 ll exgcd(ll a,ll b,ll &x,ll &y){    //模板(返回gcd(a,b))
12     if(!b){x=1;y=0;return a;}
13     ll d=exgcd(b,a%b,y,x);y-=a/b*x;return d;
14 }
15 ll excrt(){    //模板
16     ll clcm=a[1],ans=b[1];    //前一个方程合并就是第一个方程(clcm是A,ans是B)
17     for(int i=2;i<=n;i++){
18         ll x,y,d=exgcd(clcm,a[i],x,y),r=((b[i]-ans)%a[i]+a[i])%a[i],tmp=clcm/d*a[i];    //x,y,d是EXGCD中的,r是bi-B,tmp是lcm(A,ai)
19         if(r%d) return -1;    //裴蜀定理,不整除gcd则无解
20         x=(qmul(x,r/d,a[i])+a[i])%a[i];    //真正的k'
21         ans=(ans+qmul(x,clcm,tmp))%tmp;    //新的B
22         clcm=tmp;    //新的A
23     }
24     return ans;    //滚出来的B
25 }
26 int main(){
27     scanf("%d",&n);
28     for(int i=1;i<=n;i++) scanf("%lld%lld",a+i,b+i);
29     printf("%lld
",excrt());
30 }
EXCRT

其实我学这个是为了做出这题:

洛谷P4774 BZOJ5418 LOJ2721 [NOI2018]屠龙勇士 题解

原文地址:https://www.cnblogs.com/1000Suns/p/9461581.html