扩展中国剩余定理
前置芝士
中国剩余定理
作用
求同余方程余数无限制的通解。
推导过程
设 (k-1) 个方程解为 (x) ,令 (m=prod_{i=1}^{k-1}m_i) (注:此处不是乘积,而是 (lcm(m_1,m_2,...,m_{k-1})))
我们有 (x+i*m(iin Z)) 为前 (k-1) 个方程的通解
对于 (k) 个方程,求:
[tin Z,x+t*mequiv a_k~(mod~m)
]
可化为:
[t*mequiv a_k-x~(mod~m_k)
]
由裴蜀定理得,方程若有解,则 (gcd(m,m_k)|a_k-x)
通过 (exgcd) ,我们可以求得:
[t*m+b*m_k=gcd(m,m_k)
]
的一组通解
两边同乘 (dfrac{a_k-x}{gcd(m,m_k)}) ,得:
[t*m*frac{a_k-x}{gcd(m,m_k)}+b*m_k*frac{a_k-x}{gcd(m,m_k)}=a_k-x
]
由数学归纳法,循环 (n-1) 次即可
#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int N=1e5+9;
ll n,m,ans,x,y,M;
l'l a[N],b[N];
inline ll exgcd(ll a,ll b,ll &x,ll &y){
if(!b) {x=1;y=0;return a;}
ll ret=exgcd(b,a%b,x,y);
ll z=x;x=y,y=z-(a/b)*y;
return ret;
}
int main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld%lld",&b[i],&a[i]);
ans=a[1],M=b[1];
for(int i=2;i<=n;i++)
{
ll tem=((a[i]-ans)%b[i]+b[i])%b[i];
ll G=exgcd(M,b[i],x,y);
x=x%b[i]*(tem/G);
ans=ans+M*x;
M=M*b[i]/G;//gcd(a,b)*lcm(a,b)=a*b
ans=(ans+M)%M;
}
printf("%lld
",(long long)ans);
return 0;
}
例题
P4774 [NOI2018]屠龙勇士
思路
设 (x) 为前 (i-1) 个的答案,则这个方程应为:
[b[i](x+t*M)equiv a[i]~(mod~p)
]
稍加转移,可得
[t*b[i]*M+v*p=a[i]-b[i]*x
]
然后用扩欧求解,往下递推
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100005;
int T,n,m,b[maxn],t[maxn];
ll a[maxn],p[maxn],mx;
multiset<ll>s;
inline void exgcd(ll A,ll B,ll &x,ll &y,ll &gcd){
if(!B) x=1,y=0,gcd=A;
else exgcd(B,A%B,y,x,gcd),y-=(A/B)*x;
}
ll ExCRT(){
ll ans=0,lcm=1,x,y,gcd,A,B,C;
for(int i=1;i<=n;i++){
A=(__int128)b[i]*lcm%p[i];
B=p[i];
C=(a[i]-b[i]*ans%p[i]+p[i])%p[i];
exgcd(A,B,x,y,gcd);x=(x%B+B)%B;
if(C%gcd) return -1;//如果C不是gcd(A,B)的倍数,那就不用算了
ans+=(__int128)(C/gcd)*x%(B/gcd)*lcm%(lcm*=B/gcd);
ans%=lcm;
//puts("----------");
//printf("%lld
",ans);
}
if(ans<mx) ans+=((mx-ans-1)/lcm+1)*lcm;//砍的刀数不能小于龙的血量除以攻击力的最大值
return ans;
}
int main(){
scanf("%d",&T);
while(T--){
s.clear();mx=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) scanf("%lld",&p[i]);
for(int i=1;i<=n;i++) scanf("%d",&t[i]);
for(int i=1,x;i<=m;i++) scanf("%d",&x),s.insert(x);
for(int i=1;i<=n;i++) {
if(*s.begin()>a[i])b[i]=*s.begin(),s.erase(s.begin());
else b[i]=*(--s.upper_bound(a[i])),s.erase(s.lower_bound(b[i]));
s.insert(t[i]);//求龙的血量除以攻击力的最大值
mx=max(mx,(a[i]-1)/b[i]+1);
}
//for(int i=1;i<=n;i++)
// printf("%d ",(a[i]-1)/b[i]+1);
printf("%lld
",ExCRT());
}
}
(大力感谢AlanSP)