扩展中国剩余定理

扩展中国剩余定理

前置芝士

中国剩余定理

作用

求同余方程余数无限制的通解。

推导过程

\(k-1\) 个方程解为 \(x\) ,令 \(m=\prod_{i=1}^{k-1}m_i\)注:此处不是乘积,而是 \(lcm(m_1,m_2,...,m_{k-1})\)

我们有 \(x+i*m(i\in Z)\) 为前 \(k-1\) 个方程的通解

对于 \(k\) 个方程,求:

\[t\in Z,x+t*m\equiv a_k~(mod~m) \]

可化为:

\[t*m\equiv 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\n",(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\n",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\n",ExCRT()); 
	} 
} 

(大力感谢AlanSP

posted @ 2020-07-25 17:03  jasony_sam  阅读(143)  评论(0编辑  收藏  举报