zoukankan      html  css  js  c++  java
  • 扩展中国剩余定理学习笔记

    II.exCRT(扩展中国剩余定理)

    上文我们说到,CRT仅适用于 \(m\) 两两互质的情形。那如果不保证这一限制,明显原方程是仍然有解的,如何求解呢?

    在上文的最后,我们成功将三个式的方程消到了两个,在这里能否继续?

    我们考虑这个式子:

    \[x=a+\alpha A=b+\beta B \]

    其等价于 \(\alpha A-\beta B=b-a\)\(A,B,b-a\) 均为常数。

    发现了什么?这不是exGCD的形式吗?

    于是我们用exGCD求出一组解 \(\alpha,\beta\),然后就把两个式子的方程给压到了一个。

    (附:实际上,其还等价于 \(\alpha A\equiv b-a\pmod{B}\),进一步等价得 \(\alpha\dfrac{A}{\gcd}\equiv\dfrac{b-a}{\gcd}\pmod{\dfrac{B}{\gcd}}\)

    代码实现时需要非常小心,要保证每个东西都模上了它所能模的最小的东西。有些地方必须用快速乘来保证不爆 long long

    II.I.【模板】扩展中国剩余定理(EXCRT)

    就是模板啦。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n;
    ll a[100100],m[100100];
    ll exGCD(ll a,ll b,ll &x,ll &y){
    	if(!b){x=1,y=0;return a;}
    	ll tmp=exGCD(b,a%b,y,x);y-=a/b*x;return tmp;
    }
    ll ksc(ll x,ll y,ll mod){
    	ll z=0;
    	for(;y;y>>=1,x=(x<<1)%mod)if(y&1)(z+=x)%=mod;
    	return z;
    }
    void exCRT(int i){//merge equation i and i+1
    	ll A=m[i],B=m[i+1],alpha,beta,c=(a[i+1]-a[i]%B+B)%B;
    	ll gcd=exGCD(A,B,alpha,beta);
    	ll newb=B/gcd;
    //	printf("%lld %lld %lld %lld:%lld\n",A,alpha,B,beta,gcd);
    //	printf("%lld %lld\n",a[i],a[i+1]);
    	alpha%=newb;if(alpha<0)alpha+=newb;
    	alpha=ksc(alpha,c/gcd,newb);
    	m[i]*=newb;
    	(a[i]+=ksc(alpha,A,m[i]))%=m[i];
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)scanf("%lld%lld",&m[i],&a[i]);
    	for(int i=n-1;i;i--)exCRT(i);
    	printf("%lld\n",a[1]);
    	return 0;
    }
    

    II.II.[NOI2018] 屠龙勇士

    首先,可以轻松发现,每条龙用来砍的剑是唯一的。故设这把剑的攻击力为 \(atk_i\)

    先不管砍 \(x\) 刀后龙的血量是否会到达负数,明显一个合法的 \(x\) 必满足 \(a_i-atk_i\times x\equiv 0\pmod{p_i}\)

    其等价于 \(kp_i+atk_i\times x=a_i\)。是exGCD的形式,直接扩欧一波带走。

    明显,在扩欧的形式下,这合法的 \(x\) 应有 \(x\equiv rm_i\pmod{md_i}\),其中 \(rm_i\)\(md_i\) 可以直接由上式求出。

    于是就回到了我们熟悉的exCRT的形式。

    需要注意的是,在最终求出了符合条件的 \(x\) 应有 \(x\equiv A\pmod M\) 时,要注意 \(A\) 可能不是符合题意的解,可能要往上面加若干个 \(M\) 才能将所有龙的血量砍到负数。具体就直接对每条龙需要砍的次数取一个 \(\max\),然后找到比此 \(\max\) 更大的 \(x\) 即可。

    另外,在求 \(atk_i\) 的时候,应该要用到一个 multiset。如果你像我一样,看到攻击力都在 int 范围内就套了个 int 进去的话是不行的,因为你在里面 upper_bound 的时候,如果你传进去的是 long long 的话,STL 会自动强转成 int 然后再 upper_bound!因此,只能选择在里面套上 long long

    时间复杂度 \(O(n\log n)\)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int T,n,m;
    ll a[100100],p[100100],atk[100100],bon[100100];
    multiset<ll>s;
    ll exGCD(ll a,ll b,ll &x,ll &y){
    	if(!b){x=1,y=0;return a;}
    	ll tmp=exGCD(b,a%b,y,x);y-=a/b*x;return tmp;
    }
    ll rm[100100],md[100100];
    ll ksc(ll x,ll y,ll mod){
    	ll z=0;
    	for(;y;y>>=1,(x<<=1)%=mod)if(y&1)(z+=x)%=mod;
    	return z;
    }
    bool prep(){
    	for(int i=1;i<=n;i++){
    		ll A=atk[i],B=p[i],C=a[i],u,v;
    		ll gcd=exGCD(A,B,u,v);
    		if(C%gcd)return false;
    		ll np=B/gcd;
    		u%=np;if(u<0)u+=np;
    		u=ksc(u,(C/gcd)%np,np);
    		rm[i]=u,md[i]=np;
    	}
    	return true;
    }
    ll exCRT(){
    	ll X=0,M=1;
    	for(int i=1;i<=n;i++){
    		ll A=M,B=md[i],C=(rm[i]-X%B+B)%B,x,y;
    		ll gcd=exGCD(A,B,x,y);
    		if(C%gcd)return -1;
    		ll np=B/gcd;
    		x%=np;if(x<0)x+=np;
    		x=ksc(x,(C/gcd)%np,np);
    		M*=np;
    		(X+=ksc(x,A,M))%=M;
    	}
    //	printf("%lld %lld\n",X,M);
    	ll tms=0;
    	for(int i=1;i<=n;i++)if(X*atk[i]<a[i])tms=max(tms,((a[i]-1)/atk[i]-X)/M+1);
    	return X+tms*M;
    }
    int main(){
    //	freopen("P4774_18.in","r",stdin);
    	scanf("%d",&T);
    	while(T--){
    		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("%lld",&bon[i]);
    		for(int i=1,x;i<=m;i++)scanf("%lld",&x),s.insert(x);
    		for(int i=1;i<=n;i++){
    			set<ll>::iterator it;
    			if(*s.begin()>a[i])it=s.begin();else it=--s.upper_bound(a[i]);
    			atk[i]=*it,s.erase(it);
    			s.insert(bon[i]);
    		}
    		s.clear();
    		if(!prep()){puts("-1");continue;}
    //		for(int i=1;i<=n;i++)printf("(%lld,%lld,%lld)\n",a[i],atk[i],p[i]);
    //		for(int i=1;i<=n;i++)printf("%lld %lld\n",rm[i],md[i]);
    		printf("%lld\n",exCRT());
    	}
    	return 0;
    }
    

  • 相关阅读:
    linux 计划任务
    linux 进程管理
    PHP中global与$GLOBALS['']的区别
    php预定义变量
    linux 强制终止进程命令
    mysql取某表中数据的随机的方法
    mysql 连接 选库 查询
    Python 的异步 IO:Asyncio 简介
    并发和并行的区别
    asyncio模块中的Future和Task
  • 原文地址:https://www.cnblogs.com/Troverld/p/14620896.html
Copyright © 2011-2022 走看看