CRT&&EXCRT学习笔记

这是个大坑,今天填了,本来应该好久以前就会的(但硬是没学)


关于CRT

CRT就是中国剩余定理,是一个没什么泛用性但是偶尔会考的东西

(相比之下没怎么考过的欧拉函数就显得比较惨)

其中mi两两互质,求最小的x

首先构造M为所有mi的乘积,然后Mi为M/mi,易得Mi和mi互质,

因此必有bi使Mi*bi = 1(mod mi) 即bi为模mi意义下Mi的逆元,

所以可以得到Mi*bi*ai = ai(mod mi)

把所有Mi*bi*ai相加就是一个合法解x(这是一个没有证的东西)

为了使x为最小正整数,所以x还要模mi的LCT(不是指LINK-CUT-TREE)

即x = (x%M+M)%M(因为mi两两互质所以LCT就是M)

愉快。


Luogu P3868 猜数字

裸体板子,式子移个项就是CRT

但是出题人很鹅心,要用龟速乘处理一个爆longlong的点,还有就是要时刻注意逆元的正负和大小

对于一个最小正整数逆元可以x[i] = (x[i]%m[i] + m[i]) % m[i](相当于调整EXGCD中x和y)

丢人蒟蒻把乘的式子写错了(快滚快滚)

#include<bits/stdc++.h>

using namespace std ;

const int MAXN = 20;
long long K,a[MAXN],Mi[MAXN],M,m[MAXN],ans,x[MAXN];

void exgcd(long long a,long long b,long long &x,long long &y){
    if(b==0){
        x=1,y=0;
        return;
    }
    exgcd(b,a%b,x,y);
    long long xx=x,yy=y;
    x=yy;
    y=xx-(a/b)*yy;
    return ;
}

long long inv(long long a,long long mod){
    long long xxx=0,y=0;
    exgcd(a,mod,xxx,y);
    return xxx;
}

long long qm(long long a,long long b,long long mod){
    long long ret=0;
    while(b){
        if(b&1) (ret+=a)%=mod;
        a=(a+a)%mod;
        b>>=1;
    }
    return ret;
}

int main(){
    ios::sync_with_stdio(false);
    cin>>K;
    for(int i=1;i<=K;i++){
        cin>>a[i];
    }
    for(int i=1;i<=K;i++){
        cin>>m[i];
    }
    M=1;
    for(int i=1;i<=K;i++){
        M *= m[i];
    }
    for(int i=1;i<=K;i++){
        Mi[i] = M / m[i];
    }
    for(int i=1;i<=K;i++){
        x[i] = inv(Mi[i],m[i]);
//        cout<<"Mi[i] = "<<Mi[i]<<"m[i] = "<<m[i]<<endl;
//        cout<<"x[i] = "<<x[i]<<endl;
        x[i] = (x[i]%m[i] + m[i]) % m[i];
    }
    for(int i=1;i<=K;i++){
        ans += qm(qm(a[i],Mi[i],M),x[i],M);
        ans %= M;
    }
    
//    cout<<"M = "<<M<<endl;
//    for(int i=1;i<=K;i++){
//        cout<<"m"<<i<<" = "<<m[i]<<"M"<<i<<" = "<<Mi[i]<<" "<<endl;
//    }
//    for(int i=1;i<=K;i++){
//        cout<<"x"<<i<<" = "<<x[i]<<" "<<endl;
//    }
    cout<<ans<<endl;
    return 0;
}
//凝聚SB气息的调试信息...

这个是CRT,只适用于m两两互质的情况,如果m没有任何特殊条件呢?

这就是EXCRT了


关于EXCRT

把EXCRT要干的的事情用式子写出来就是

其中mi不一定两两互质,求最小的正整数解x

网上看了几个版本,有一种合并的求x方式,同机房几个大佬都写过了,这里不再赘述

但有一种容易理解的写法(niiick的博客),感觉非常好

大概是这样的:

对于第一个式子解为a[1]%m[1](是啊仔细想想)

如果对于前k-1个式子已经有了一个最小非负整数解x,并且设M为前k-1个m的乘积,

那么对于前k-1个式子来说,所有解x的表达式为 {x(all)} = x + O * M (O为整数)

现在要用这个表达式,找一个解x,使对于前k个式子来说,x = a[i] (mod m[i]) (1<=i<=k)

(我这种暴力选手看到这里直接想向上枚举了,但是经验告诉我们不能乱搞)

可以把对于前k-1个式子都合法的那个x叫做x0(没有Markdown原谅下⑧)

那么我们要求x0 + t * M = a[k] (mod m[k])

    变形为t * M + O * m[k] = a[k] - x0 (O为任意整数系数)

首先判断a[k]-x0是不是gcd(M,m[k])的倍数,不是则整个方程式无解

是则EXGCD求解t,

然后处理t使其最小正整数(与m[k]交易),

然后x += t * M,处理M,接着处理下一个式子

最后全部式子处理完之后得到答案x

也比较愉快。去看题


Luogu P4777 【模板】扩展中国剩余定理(EXCRT)

数据范围比CRT大多了,同时观察到a和b的给出方式利于在线处理

还有一堆小细节要处理,要乘什么,式子表示的是不是对的

当前系数表示的东西是什么,怎么维护才是正确的

这一步要不要取模,balabala,还有,HAVE FUN DEBUGING

 

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 100010;
long long n,ai[MAXN],bi[MAXN];

long long qm(long long a,long long b,long long mod){
    long long ret=0;
    while (b){
        if(b&1) (ret += a) %= mod;
        a = (a+a) % mod;
        b>>=1;
    }
    return ret;
}

long long  exgcd(long long  a,long long  b,long long  &x,long long  &y) { 
    if(b==0){x=1;y=0;return a;} 
    long long  gcd=exgcd(b,a%b,x,y); 
    long long  tp=x; x=y; y=tp-a/b*y; 
    return gcd;
} 

long long  excrt() { 
     long long x,y,k;
      long long M=bi[1],ans=ai[1];
       for(int i=2;i<=n;i++) { 
           long long  a=M,b=bi[i],c=(ai[i]-ans%b+b)%b;
        long long  gcd=exgcd(a,b,x,y),bg=b/gcd;
//         if(c%gcd!=0) return -1;
          x=qm(x,c/gcd,bg); 
          ans+=x*M; 
          M*=bg; 
          ans=(ans%M+M)%M;
    } 
    return (ans%M+M)%M; 
}

signed main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>bi[i]>>ai[i];
    }
    cout<<excrt()<<endl;
    return 0;
}

 TAG:SIN_XIII ⑨

 

原文地址:https://www.cnblogs.com/SINXIII/p/11233857.html