Luogu P1495 曹冲养猪

https://www.luogu.com.cn/problem/P1495

中国剩余定理(CRT)是个什么东西呢?

\[\left\{ \begin{array}{c} x\equiv a_1(mod\;\;m_1)\\ x\equiv a_2(mod\;\;m_2)\\ \cdots\\ x\equiv a_n(mod\;\;m_n) \end{array} \right. \]

其中:\(m_1,m_2,……,m_n\)两两互质

让我们求一个最小的非负整数\(x\),使得这个方程组成立


神葱曰:

大数翻倍法

具体步骤:

将这\(n\)个方程按\(m_i\)从大到小排序,使得:

\[m_1\geq m_2 \geq ……m_n \]

假设我们现在只有两个方程组:

我们令\(x=k×m_1+a_1\),找到一个最小的\(k\),使得:

\(k×m_1+a_1=a_2(mod\;\;m_2)\)

这样\(x\)就满足了两个方程

\(k\)枚举的上限是多少呢?

\[∵0×m_1+a_1=m_2×m_1+a_1(mod\;\;m_2) \]

所以这个东西是有循环节的

答案一定是在\(=>[0,m_2]\)这段区间内的

\(k\)最多枚举\(m_2\)

否则,我们就可以说它无解

因此我们做\(n-1\)次这样的操作,即可把\(n\)个方程组解出

时间复杂度:\(O(\sum_{i=1}^n m_i -max(m_1,\cdots,m_n))\)


但这个算法什么时候会T呢?

我们发现一个神奇的事情:\(n\)越大,这个算法越快

因为出题人必须满足一个条件:

\[\prod_{i=1}^n m_i \leq long\;long_{max} \]

所以当\(n\)较大时,\(\sum_{i=1}^n m_i\)会较小。

只有当\(n=2,m_1,m_2\leq10^9\)时才有可能会T


代码:

#include
#include
#include
#include
using namespace std;
const int N=20;
#define LL long long
int n;
struct node
{
    LL m,r;//模数与余数
}p[N];
LL res,add;
bool cmp(node A,node B)
{
    return A.m>B.m;//按mi排序
}
LL gcd(LL A,LL B)
{
    if(B==0)return A;
    return gcd(B,A%B);
}
LL lcm(LL A,LL B)
{
    return A*B/gcd(A,B);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld%lld",&p[i].m,&p[i].r);
    res=p[1].r;add=p[1].m;
    for(int i=2;i<=n;i++)
    {
        while(res%p[i].m!=p[i].r)
        {
        //只要不等于余数,就不停地加
        (枚举k)
            res=res+add;
        }
        add=lcm(add,p[i].m);
        //注意:此时加数也要累积
    }
    printf("%lld",res);
    return 0;
}

思考有没有什么其它的做法?

定理:若\(m_1,m_2,……,m_n\)是两两互质的正整数

\(C=\prod_{i=1}^n m_i,M_i =\frac{C}{m_i},t_i×M_i=1(mod\;\;m_i)\)

方程组的解为:\(\sum_{i=1}^n a_i M_i t_i\)

且在\(mod\;\;C\)意义下有唯一正整数解

自己代入一下即可

如何算\(t_i\)

其实\(t_i\)就是\(M_i\)\(mod\;\;m_i\)意义下的逆元

费马小定理?但前提是\(m_i\)是质数,题目中没有保证

对于这道题可以用\(exgcd\)


代码:

#include
#include
#include
#include
using namespace std;
const int N=20;
#define LL long long
int n;
LL m[N],a[N],C=1,M[N],t[N];
LL exgcd(LL a,LL b,LL &x,LL &y)//扩展欧几里得
{
    if(b==0)
    {
        x=1;y=0;
        return a;
    }
    LL d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>m[i]>>a[i];
        C*=m[i];
    }
    for(int i=1;i<=n;i++)
    {
        M[i]=C/m[i];
        LL x,y;
        exgcd(M[i],m[i],x,y);//exgcd求逆元
        t[i]=x;
    }
    LL res=0;
    for(int i=1;i<=n;i++)
    {
        res=res+(a[i]*M[i]*t[i]);
    }
    cout<<(res%C+C)%C<

EXCRT(扩展中国剩余定理)

这是个什么东西呢?

唯一比CRT多的一点就是它不能保证\(m_i\)两两互质

在这里,我们就不能直接构造出一个解了,因为\(M_i\)\(m_i\)不一定互质,因此\(M_i\)不一定有逆元

如何做呢?


假设我们现在已经求出了前k-1个方程的一个解x

而:\(lcm(m_1,m_2,……,m_{k-1})=M\)

则我们现在要找出一个最小的正整数\(T\),使得:

\[x+T×M=a_k\;(mod\;\;m_k) \]

推导一下,即:

\[x+T×M+y×m_k=a_k \]

\[T×M+y×m_k=a_k-x \]

仔细观察即可发现,这是exgcd的一般形式:

\[ax+by=gcd(a,b) \]

这里\(M=a,m_k=b,a_k-x=g×p\),我们直接用exgcd求解即可

根据裴蜀定理:\(ax+by=g\)的充要条件是\(gcd(a,b)|g\)

而题目中保证有解

所以\(p\)一定是整数,所以答案就是\(x×(g÷gcd(a,b))\)


代码:

#include
#include
#include
#include
using namespace std;
const int N=100010;
#define LL long long
LL n,m[N],r[N],res,M;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    LL d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}//扩展欧几里得
LL mul(LL a,LL b,LL mod)
{
    LL ans=0;
    while(b)
    {
        if(b&1)ans=(ans+a)%mod;
        a=(a+a)%mod;
        b>>=1;
    }
    return ans;
}//龟速乘
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld%lld",&m[i],&r[i]);
    res=r[1];M=m[1];//初始
    for(int i=2;i<=n;i++)
    {
        //ax+by=g
        LL a=M,b=m[i],g=((r[i]-res)%b+b)%b;
        //前面推导的式子
        LL x,y; 
        LL d=exgcd(a,b,x,y),t=b/d;
        x=mul(x,g/d,b);//g÷d一定是整数(裴蜀定理)
        //由于可能会乘法溢出,所以x要模上mi
        res+=x*M;//答案加上我们求的解
        M*=t;//计算最小公倍数
        res=(res%M+M)%M;//不停地模M
    }
    printf("%lld",(res%M+M)%M);
    return 0;
}

你可能感兴趣的:(Luogu P1495 曹冲养猪)