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

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

题目描述

给定 \(n\) 组非负整数 \(a_i, b_i\) ,求解关于 \(x\) 的方程组的最小非负整数解。

\[\begin{cases}x\equiv b_1\pmod{a_1}\\x\equiv b_2\pmod{a_2}\\\dots\\x\equiv b_n\pmod{a_n}\end{cases} \]

提示

对于 \(100 \%\) 的数据,\(1 \le n \le {10}^5\)\(1 \le b_i,a_i \le {10}^{12}\),保证所有 \(a_i\) 的最小公倍数不超过 \({10}^{18}\)

请注意程序运行过程中进行乘法运算时结果可能有溢出的风险。

数据保证有解。

说句闲话:

非常好的数学题,使我的大脑旋转。
感觉梦回小学奥数了:有一堆苹果,,3个一组剩1个,5个一组剩3个......

Solution:

先膜亿膜我认为最好的题解,十分美味可口,适合我这样的蒟蒻。

我们先转换一下原方程:

\[\left\{ \begin{aligned} x\equiv b \ (mod \ a) \\ x\equiv B \ (mod \ A) \end{aligned} \right. \]

\[\left\{ \begin{aligned} x=k\times a+b \\ x=K\times A+B \end{aligned} \right. \]

\[ka+b=+KA+B \]

\[ak+A(-K)=B-b \]

然后我们就可以用 exgcd 求出
\(ak+A(-K)=gcd(a,A)\)的一组特解\((k_0,-K_0)\)

然后记 \(q=\frac{B-b}{gcd(aA)}\)
回代:

\[a(k_0\times q)+A(-K \times q)=gcd(a,A)\times \frac{B-b}{gcd(a,A)}=B-b \]

\(x=ak+b\) ,所以我们希望最小化 \(x\) 就是要最小化 \(k\).

然后 \(k\) 的通解:\(k=k_0+ t\times\frac{A}{gcd(a,A)}\)

所以有:

\[k_{min}=k_0 mod \frac{A}{gcd(a,A)} \]

然后这两个方程就被合并为了:

\[x\equiv a\times k_{min} + b \ (mod \ lcm(a,A)) \]

至于最后的答案统计嘛:

\[x\equiv a\times k_{min} + b \ (mod \ lcm(a,A)) \]

在最后一次 merge 做完之后,\(a=lcm(a_1....a_n)\),

所以有:

\[x\equiv a\times k_{min} + b \ (mod \ a) \]

\[x=a*tmp+b \]

那么我们只需要在保证\(x\ge0\)的情况下最小化 \(tmp\) 就好了,其实就是\(x=b \ mod \ a\)

然后这题就愉快的做完了。

Code:

#include<bits/stdc++.h>
#define ll __int128
using namespace std;
ll read()
{
    ll res=0;char c;
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9'){res=res*10+c-'0';c=getchar();}
    return res;
}
void exgcd(ll &x,ll &y,ll a,ll b)
{
    if(!b)x=1,y=0;
    else {exgcd(y,x,b,a%b);y-=a/b*x;}
}
ll gcd(ll x,ll y)
{
    return y ? gcd(y,x%y) : x;
}
ll lcm(ll x,ll y)
{
    return x/gcd(x,y)*y;
}
ll a,b,A,B,x,y;
void merge()
{
    exgcd(x,y,a,A);
    ll d=gcd(a,A),mod=lcm(a,A),c=B-b,p=c/d,q=A/d;
    if(c%d){cout<<-1;exit(0);}
    x=x*p%q;x = x<0 ? x+q : x;
    b=(a*x+b)%mod;b = b<0 ? b+mod : b;
    a=mod;
}
int n;
void work()
{
    cin>>n;
    a=read(),b=read();
    for(int i=2;i<=n;i++)
    {
        A=read(),B=read();
        merge();
    }
    long long ans=(long long )(b%a);
    cout<<ans;
}
int main()
{
    //freopen("excrt.in","r",stdin);freopen("excrt.out","w",stdout);
    work();
    return 0;
}

posted @ 2025-02-07 18:38  liuboom  阅读(27)  评论(0)    收藏  举报