数论三连3·CRT和EXCRT

蒟蒻的我终于调出了屠龙勇士。。数论三连终于可以集齐了,留念一下。。。

CRT:构造啊构造。。害怕。。

有一堆形如ai*x=ci mod bi的东西。(ai,bi两两互质)

设M=b1*b2*..*bn;Mi=M/bi; 那么其实就是求解ai*(Mi*x')=ci mod bi->x'=ci*inv(ai*Mi), 

于是通解x=sum(ci*inv(ai*Mi)*Mi)+k*M。

EXCRT:其实我觉得CRT还是EXCRT好写。害怕。。

首先可以将ai*x mod ci=bi转化成x mod ..=..的形式。

怎么转化呢? ai*x+bi*y=ci; ->d=gcd(ai,bi,ci); 将aibici都除以d,

则ai'*x+bi'*y=ci';此时ai'和bi'一定互质,否则就无解。

为什么?如果ai'和bi'仍不互质,则gcd(gcd(ai',bi'),ci')=1,而gcd(ai',bi')!=1,那么gcd(ai',bi')必然不能整除ci,方程无解。

化到这里以后,就可以求逆元转化成x =ci*inv(ai') mod bi。

然后使用数学归纳法。前i个方程的通解形式可以表现为x0+k*m,其中x0是一个已求好的特解,m是lcm(bi)。

然后对于下一个方程j,必然有(x0+k*m)=cj mod bj -> mk=cj-x0 mod bj,exgcd求出随便某个k,x0'=x0+k*m,就得到了新的特解。

最后将特解模m就可以了。

为何可以表现成x0+k*m这种形式?首先对于第一个方程当然是成立的。对于后面的方程,假设前i个方程可以这样表示,

mk=cj-x0 mod bj->mk+by=c-x0; 这个方程的通解形式是k=k0+t*(lcm(m,b)/m);于是对于所有x0+k*m都可以表示成

x0+k0*m+t*lcm(m,b)=x0'+t*lcm(m,b),证完了。

最后吐槽一下调不出屠龙勇士的原因->在使用multiset极其傻叉地erase了元素值。。。是菜的啊。。。

luogu4777的EXCRT模板:

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline LL read(){
	LL x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int N=100005;
int n;
struct node{ LL a,b; }o[N];
LL multi(LL a,LL b,LL c){
	a=(a%c+c)%c; b=(b%c+c)%c; 
	LL res=0; while (b){ if (b&1) res=(res+a)%c; a=(a+a)%c; b>>=1; }
	return (res%c+c)%c;
}
void exgcd(LL a,LL b,LL c,LL &x,LL &y){
	if (!a){ x=0; y=c/b; return; }
	if (!b){ y=0; x=c/a; return; }
	exgcd(b,a%b,c,y,x);
	y-=(a/b)*x;
}
LL gcd(LL a,LL b){ return b? gcd(b,a%b):a; }
LL lcm(LL a,LL b){ return (a/gcd(a,b))*b; }
LL excrt(node *o,int n){
	//x'=x+k*m=a (mod b) mod lcm(m,b); m*k=a-x (mod b);
	LL m=o[1].b; LL x=(o[1].a%m+m)%m;
	rep(i,2,n){
		o[i].a%=o[i].b; LL newm=lcm(m,o[i].b); 
		LL a=m%o[i].b; LL b=o[i].b; LL c=((o[i].a-x)%o[i].b+o[i].b)%o[i].b; 
	    LL d=gcd(a,b); if (c%d) return -1; a/=d; b/=d; c/=d;
		LL X,Y; exgcd(a,b,1,X,Y); X=multi(X,c,newm); 
		x=((x+multi(X,m,newm))%newm+newm)%newm; m=newm;
	}   
	return x;
}
int main(){
	n=read(); rep(i,1,n) o[i].b=read(),o[i].a=read();
	printf("%lld\n",excrt(o,n)); return 0;
}

NOI2018屠龙勇士:(吐槽:网络赛的时候一看此题突然脑子一抽想到了同余方程。。然后当时不知道EXCRT。。手推了

鬼畜的exgcd反复带入解同余方程的奇怪东西。。。怒调1小时过了样例就很开心地交了,连快速乘都没加,然后成功被卡成0分,然而EXCRT是多么地简洁,真是一个悲伤的故事)

#include<bits/stdc++.h>
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline LL read(){
	LL x=0; int w=0; char ch=0;
	while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w? -x:x;
}
const int N=200005;
int n,m;
multiset<LL> s;
LL ATK[N],a[N],b[N],c[N];
struct node{ LL a,b; }o[N];
LL multi(LL a,LL b,LL c){
	LL res=0; LL w=(b<0)? (-1):1; if (b<0) b=-b;
	while (b){ if (b&1) res=(res+a)%c; a=(a+a)%c; b>>=1; } 
	return ((res*w)%c+c)%c;
}
LL gcd(LL a,LL b){ return b? gcd(b,a%b):a; }
LL lcm(LL a,LL b){ return (a/gcd(a,b))*b; }
void exgcd(LL a,LL b,LL c,LL &x,LL &y,LL d){
	if (!a){ x=0; y=c/b; return; }
	if (!b){ y=0; x=c/a; return; }
	exgcd(b,a%b,c,y,x,d); y=(y-multi(a/b,x,d))%d;
}
//x=a mod b
LL excrt(node *o,int n){
	LL x=0; LL m=1; 
	rep(i,1,n){ 
		LL a=m%o[i].b; LL b=o[i].b; LL c=(o[i].a-x)%o[i].b;
		if (!a){ m=lcm(m,o[i].b); if (c) return -1; continue; }
		LL d=gcd(a,b); if (c%d) return -1; a/=d; b/=d; c/=d;
		LL newm=lcm(m,o[i].b); LL X,Y; exgcd(a,b,1,X,Y,newm); X=multi(X,c,newm);
		x=((x+multi(X,m,newm))%newm+newm)%newm; m=newm;  
	}
	x=(x%m+m)%m; LL tmp=0;
	double res=0; rep(i,1,n) res=max(res,(double)c[i]/a[i]); 
	rep(i,1,n){
		double t1=c[i]-a[i]*x; double t2=a[i]*m; 
		tmp=max(tmp,(LL)ceil(t1/t2));
	}
	return x+tmp*m;
}
LL inv(LL u,LL c){
	LL x,y; exgcd(u,c,1,x,y,c); return (x%c+c)%c; 
}
LL solve(int t){
	LL x=a[t]; LL y=b[t]; LL z=c[t];
	LL d=gcd(gcd(x,y),z); x/=d; y/=d; z/=d; 
	if (abs(gcd(x,y))!=1) return -1; 
	o[t].a=multi(z,inv(x,y),y); o[t].b=y;
}
int main(){
	int cas=read();
	rep(cl,1,cas){
		n=read(); m=read(); s.clear();
		rep(i,1,n) c[i]=read(); rep(i,1,n) b[i]=read(); 
		rep(i,1,n) ATK[i]=read(); rep(i,1,m) { LL x=read(); s.insert(x); }
		rep(i,1,n){
			multiset<LL> :: iterator nw=s.upper_bound(c[i]);
			if (nw!=s.begin()) --nw; a[i]=(*nw); s.erase(nw); s.insert(ATK[i]);
		}
		int pd=1; rep(i,1,n) if (solve(i)==-1) { puts("-1"); pd=0; break; }; if (!pd) continue;
		printf("%lld\n",excrt(o,n));
	}
	return 0;
}  

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值