[笔记] CRT & exCRT

[笔记] CRT & exCRT

构造法

求多组\(x \equiv r_i (\bmod d_i)\)的解,\(d_i\)互质

余数\((r_i = remainder)\),除数\((d_i=divisor)\)

我们想啊,如果我们能找到一个数 \(k1\equiv1(mod\text{ }3)\)\(5\)\(7\) 的倍数

一个数 $k2\equiv1(mod\text{ }5) $是\(3\)\(7\)的倍数

一个数 $k3\equiv1(mod\text{ }7) $是\(3\)\(5\)的倍数

那么这样的话我们的答案就是 \(k1*2+k2*3+k3*2\)

这样的话就是如何求这三个数了

首先我们求出 \(3,5,7\) 的$ lcm=105$

之后令

\(x1=105/3=35,x2=105/5=21,x3=105/7=15\)

我们可以得到三个线性同余方程

\(35a\equiv1(mod\text{ }3)\)

\(21b\equiv1(mod\text{ }5)\)

\(15c\equiv1(mod\text{ }7)\)

直接上扩欧就行了

所以$ k1=35a=35*2=70$

\(k2=21b=21∗1=21\)

\(k3=15c=15∗1=15\)

之后我们的答案就是

\((k1∗2+k2∗3+k3∗2)\bmod lcm=23\)

曹冲养猪

板子from wzx(我的构造法过不了,不过有了扩欧也不想用这个方法了)

#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
#define re register
#define maxn 25
using namespace std;
inline LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    LL r=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return r;
}
inline LL read()
{
    char c=getchar();
    LL x=0;
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9')
      x=(x<<3)+(x<<1)+c-48,c=getchar();
    return x;
}
LL a[maxn],b[maxn];
LL x,y;
int n;
LL lcm=1;
int main()
{
    n=read();
    for(re int i=1;i<=n;i++)
    {
        b[i]=read();
        a[i]=read();
        lcm*=b[i];
    }
    LL ans=0;
    for(re int i=1;i<=n;i++)
    {
        LL xx=lcm/b[i];
        LL r=exgcd(xx,b[i],x,y);
        x=(x%b[i]+b[i])%b[i];
        ans=(ans+x*xx*a[i]%lcm)%lcm;
    }
    cout<<ans<<endl;
    return 0;
}

扩欧法

\[\begin{cases} x &\equiv& r_1 \pmod{d_1}\\ x & \equiv& r_2 \pmod{d_2}\\ &\vdots\\ x & \equiv& r_n \pmod{d_n} \end{cases}\]

此时如果\(d_1,d_2,\dots,d_n\)不互质怎么办?

观察两个同余式

  • $x\equiv r_1 \pmod{d_1} $
  • \(x\equiv r_2 \pmod{d_2}\)

可以化为

  • \(x=k_1d_1 + r_1\)
  • \(x=k_2d_2+r_2\)

因此

  • \(k_2d_2+r_2=k_1d_1+r_1\)
  • \(k_2d_2-k_1d_1=r_1-r_2\)

是不是有点像\(ax+bx=c\)

于是我们可以解出来

  • \(k_2 ^{'} d_2+k_1 ^{'} d_1=gcd(d_1,d_2)\)

并乘上\((r_1-r_2)/gcd(d_1,d_2)\)得到之前式子的解

  • \(k_1=k_1 ^{'} \cdot (r_1-r_2)/gcd(d_1,d_2)\)

但是得\(gcd(d_1,d_2)|(r_1-r_2)\),不然是得不到通解的

因为是带着一个负号的,所以得到

  • \(x_0= -k_1d_1+r_1\)

得到新方程\(x\equiv x_0 \pmod{ lcm(d_1,d_2)}\)

好的,开始,抠细节

  • 首先,最后要取模\(lcm(d1,d2)\),快速乘的时候忘记了,挂掉x1
  • 快速乘里面不能有负数,挂掉x2
  • 快速乘也要取模\(lcm(d_1,d_2)\),挂掉x3
  • \(lcm(d_1,d_2)\)应该这么写,\(d_1 /gcd(d_1,d_2)\cdot d_2\),而不是\(d_1 \cdot d_2 / gcd(d_1,d_2)\),挂掉x4
  • 把除号放里面,不然有逆元,(其实是因为模了一个不该模的东西)挂掉x5

代码

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

ll n, x, y;
ll d[N], r[N];

ll mul(ll a, ll b, ll p){//龟速乘
    int f = 1;
    if(a < 0) f = -f, a = -a;
    if(b < 0) f = -f, b = -b;
    ll w = 0;
    while(b){
        if(b & 1)
            w = (w + a) % p;
        b >>= 1;
        a = (a + a) % p;
    }
    return w * f;
}

ll exgcd(ll a, ll b){//扩欧
    ll ans, t;
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    } else {
        ans = exgcd(b, a % b);
        t = x;
        x = y;
        y = t - a / b * y;
    }
        
    return ans;
}

ll exCRT(){
    
    for(int i = 2; i <= n; ++i){
        ll C = r[1] - r[i];
        ll D = exgcd(d[i], d[1]);
        if(C % D) return -1;
        ll k1 = mul(y , c / D, d[1] / D * d[i] );
        ll x0 = mul(-k1 , d[1], d[1] / D * d[i] ) + r[1];
        d[1] = d[1] / D * d[i], r[1] = x0;
        r[1] = (r[1] % d[1] + d[1]) % d[1];//先模一遍,让绝对值小于,然后处理
    }
    return r[1];
}

int main(){
    scanf("%lld", &n);
    for(int i = 1; i <= n; ++i){
        scanf("%lld %lld", &d[i], &r[i]);
    }
    long long ans = exCRT();
    printf("%lld", ans);
    return 0;
} 

我知道写的超级丑,比不上fym

posted @ 2018-08-25 17:23  LMSH7  阅读(214)  评论(0编辑  收藏  举报