浅谈中国剩余定理

我们先来看这样一道题目:

树王种了一棵treap,她现在决定把这棵treap改造为一棵无旋多叉triep,于是她摘下了treap的所有节点,发现如果她把节点3个3个一打包,会剩下2个节点。如果她把节点5个5个一打包,会剩下3个节点,如果把节点7个7个一打包,会剩下2个节点,求这棵treap最少有多少节点?

简化后题目就是,已知x%3=2,,x%5=3,x%7=2,求x最小值?

如何解决呢?我们设k1,k2,k3满足 k1%3=1,k1%5=0,k1%7=0,k2%5=1,k2%3=0,k2%7=0.k3%7=1.,k3%3=0,k3%5=0.

那么 k1*2+k2*3+k3*2一定是满足答案的一个解。(网上说容易意会,容易意会个鬼啊)

证明: 设x=k1*2+k2*3+k3*2。 则x%3=(k1*2+k2*3+k3*2)%3=(k1*2%3+k2*3%3+k3*2%3)%3=(2+0+0)%3=2;

其他两组解同理。

那么如何求k1,k2,k3?

我们求出LCM(3,5,7)=105,设x1=LCM(3,5,7)/3=35,x2==LCM(3,5,7)/5=21,x3==LCM(3,5,7)/7=15.

那么设k1=n1*x1,k2=n2*x2,k3=n3*x3.

k1%3=1,k2%5=1,k3%7=3,可转换为 n1*x1%3=1,n2*x2%5=1,n3*x3%7=1.

然后可以用扩展欧几里得求出n1,n2,n3.进而得到 k1,k2,k3.

最后便可得到答案,而题目的通解可表示为这个数每次都加上3,5,7的最小公倍数。

好了,现在步入正题(你没看错现在我才开始讲正经的,刚才那一堆都是铺垫)

已知   x≡a[1](mod)y[1],x≡a[2](mod)y[2],x≡a[3](mod)y[3].........x≡a[n](mod)y[n].

其中 y[1],y[2],y[3]......y[n]两两互质,求x

其算法流程如下。

1.计算LCM(y1,y2......yn)。

2.从1->n,计算f[i]=LCM(y[1[,y[2],....y[n])/y[i].

3.使用扩展欧几里得定理,对同余式b[i]*f[i]%y[i]=1,计算出b[i],进而得出k[i]=b[i]*f[i].

4.从1->n,计算x=k[1]*a[1]+k[2]*a[2]+...........k[n]*a[n].

5.对x模lcm,得到答案。

 接下来给出代码:

#include <bits/stdc++.h>
#define maxn 105
using namespace std;
int ex_gcd(int a,int b,int &x,int &y)//扩展欧几里得定理,解ax+by=c。
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    int ans=ex_gcd(b,a%b,x,y);
    int temp=x;
    x=y;
    y=temp-(a/b)*x;
    return ans;
}
int a[maxn],b[maxn],n,f[maxn];
int solve()
{
    int ans=0;
    int lcm=1;
    for(int i=1;i<=n;i++)
    {
        lcm*=a[i];//两两互质,直接乘了
    }
    for(int i=1;i<=n;i++)
    {
        f[i]=lcm/a[i];
    }
    for(int i=1;i<=n;i++)
    {
        int x,y,g;
        g=ex_gcd(f[i],a[i],x,y);
        x=(x%a[i]+a[i])%a[i];//因为扩展欧几里得求出的解可能有很多组,且可能为负,我们这步求出了一个比a[i]小且大于0的解。
        ans=(ans+x*f[i]*b[i])%lcm;
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i],&b[i]);//ans%a[i]=b[i].
    }
    int ans=solve();
    printf("%d\n",ans);
}

 接下来我们谈谈扩展中国剩余定理

今天才被扩展欧拉定理gank的我来学习扩展中国剩余定理了

已知   x≡a[1](mod)y[1],x≡a[2](mod)y[2],x≡a[3](mod)y[3].........x≡a[n](mod)y[n],求x。

现在y[1],y[2]........y[n]不互质了,一般情况的中国剩余定理不适用了,这怎么搞?

考虑 两个方程,分别为x≡a[1](mod)y[1],x≡a[2](mod)y[2].

则有x=y[1]*n1+a[1],x=y[2]*n2+a[2].

合并得 y[1]*n1+a[1]=y[2]*n2+a[2].化简:   y[1]*n1-y[2]*n2=a[2]-a[1].

使用扩展欧几里得定理 解除最小正整数解 n1,设k=y[1]*n1+a[1].

那么存在通解x=k+t*lcm(y[1],y[2]).有 x≡k(mod)lcm(y[1],y[2])。

一路合并下去就ok了。

接下来给出例题 POJ2891代码

传送门:Strange Way to Express Integers

 

#include <cstdio>
#define maxn 100005
using namespace std;
typedef long long ll;
ll ex_gcd(ll a,ll b,ll &x,ll &y)//扩展欧几里得定理,解ax+by=c。
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    ll ans=ex_gcd(b,a%b,x,y);
    ll temp=x;
    x=y;
    y=temp-(a/b)*x;
    return ans;
}
ll a[maxn],b[maxn],n,f[maxn];
ll China()
{
    ll mod=a[1],reminder=b[1];//模数为mod,余数为reminder。
    for(int i=2;i<=n;i++)
    {
        ll x,y,g,temp;
        g=ex_gcd(mod,a[i],x,y);
        temp=b[i]-reminder;
        if(temp%g!=0)
        {
            return -1;
        }
        x=x*temp/g;
        ll t=a[i]/g;
        x=(x%t+t)%t;
        reminder=x*mod+reminder;
        mod=mod/g*a[i];
        reminder%=mod;
    }
    reminder=(reminder%mod+mod)%mod;
    return reminder;
}
int main()
{
    while(~scanf("%lld",&n))
    {
        for(ll i=1;i<=n;i++)
        {
            scanf("%lld%lld",&a[i],&b[i]);
        }
        printf("%lld\n",China());
    }
}

  

posted @ 2018-10-06 21:41  行远山  阅读(474)  评论(0编辑  收藏  举报