【数学】中国剩余定理

特殊情况

我们先来看一下问题,现在有n个同余方程组(如下图)
这里写图片描述
其中m两两互质。
求解。
问题很简单,结论也很简单,我们用构造的思路来想一想,能不能整一个解x,使得其有每个方程的一部分,但是当x%mi时除了第i个方程的部分以外都被%成0,即x%mi=ai,这是可以的.
这里写图片描述
(源自百度,很清楚了)
由于aitiMi%mj=0(i!=j).且aitiMi%mi=ai.
所以x%mi=ai;
但这不仅仅是我现在想讲的,因为在很多的题目中,m往往并不互质。这时怎么办呢?
我们只有换一下思路了。

一般情况

不再考虑整个一起求,那样会变得十分的麻烦。很明显如果能够将2个方程合并成一个,那么问题就解决了。
已知:
x mod m[1] = r[1];
x mod m[2] = r[2];
根据这两个式子,存在两个整数k[1],k[2]:
x = m[1] * k[1] + r[1]
x = m[2] * k[2] + r[2]
由于两个方程的左边都是x,因此有:
m[1] * k[1] + r[1] = m[2] * k[2] + r[2]
m[1] * k[1] - m[2] * k[2] = r[2] - r[1]
由于m[1],m[2],r[1],r[2]都是常数,若令A=m[1],B=m[2],C=r[2]-r[1],x=k[1],y=k[2],
则上式变为:Ax + By = C。
可以用exgcd求出 k[1] ,k[2]
再把k[1]代入x = m[1] * k[1] + r[1],就可以求解出x。
接下来我们将这个x作为特解,扩展出一个解系:
X = x + t*lcm(m[1], m[2]) t为整数,lcm(a,b)表示a和b的最小公倍数
将其改变形式为:
X mod lcm(m[1], m[2]) = x
令M = lcm(m[1], m[2]), R = x,则有新的模方程X mod M = R
经过上述步骤,我们将x mod m[1] = r[1] x mod m[2] = r[2]
这两个方程合并为了一个新方程X mod lcm(m[1], m[2]) = x;
至此,问题的理论基础就完成了。
看看例题:

一.poj1006:Biorhythms

题目是英文,翻译过来就是:
人自出生起就有体力,情感和智力三个生理周期,分别为23,28和33天。一个周期内有一天为峰值,在这一天,人在对应的方面(体力,情感或智力)表现最好。通常这三个周期的峰值不会是同一天。
现在给出三个日期,分别对应于体力,情感,智力出现峰值的日期。然后再给出一个起始日期,要求从这一天开始,算出最少再过多少天后三个峰值同时出现。
说白了n=3时的特殊情况。

二.HDU1573:X问题

中文题,自己看。
这道题就是一道板题。代码如下:

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const long long MAXN=15;
long long N,M1,m[MAXN],r[MAXN];
long long gcd(long long a,long long b)
{
    return a%b==0?b:gcd(b,a%b);
}
void exgcd(long long a,long long b,long long &x,long long &y)
{
    if(b==0)
    {
        x=1,y=0;
        return ;
    }
    long long xx,yy;
    exgcd(b,a%b,xx,yy);
    x=yy;
    y=xx-(a/b*yy);
}
int main()
{
    long long T;
    scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld %lld",&N,&M1);
        for(long long i=1;i<=M1;i++)
            scanf("%lld",&m[i]);
        for(long long i=1;i<=M1;i++)
            scanf("%lld",&r[i]);
        long long R=r[1],M=m[1],d,c,k1,k2;
        int key=0;
        for(long long i=2;i<=M1;i++)
        {
            d=gcd(M,m[i]);
            c=r[i]-R;
            if(c%d)
            {
                key=1;
                break;
            }
            exgcd(M/d,m[i]/d,k1,k2);
            k1=(c/d*k1)%(m[i]/d);
            R=R+k1*M;
            M=M/d*m[i];
            R%=M;
        }
        if(R<0)
            R=R%M+M;
        if(key||R>N)
            printf("0\n");
        else
        {
            if(R==0)
                printf("%lld\n",N/M);
            else
                printf("%lld\n",(N-R)/M+1);
        }
    }
}
三.hihocoder1303 : 数论六·模线性方程组

这道题还是一道板题。代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const long long MAXN=1005;
long long n,m[MAXN],r[MAXN];
long long gcd(long long a,long long b)
{
    return a%b==0?b:gcd(b,a%b);
}
void exgcd(long long a,long long b,long long &x,long long &y)
{
    if(b==0)
    {
        x=1,y=0;
        return ;
    }
    long long xx,yy;
    exgcd(b,a%b,xx,yy);
    x=yy;
    y=xx-(a/b*yy);
}
int main()
{
    scanf("%lld",&n);
    for(long long i=1;i<=n;i++)
    {
        scanf("%lld %lld",&m[i],&r[i]);
    }
    long long R=r[1],M=m[1],d,c,k1,k2;
    for(long long i=2;i<=n;i++)
    {
        d=gcd(M,m[i]);
        c=r[i]-R;
        if(c%d)
            return -1;
        exgcd(M/d,m[i]/d,k1,k2);
        k1=(c/d*k1)%(m[i]/d);
        R=R+k1*M;
        M=M/d*m[i];
        R%=M;
    }
    if(R<0)
        R=R+M;
    printf("%lld",R);
}
四.HDU3579:Hello Kiki

题意:给定N组数(ai, ri)。求一个最小的正整数K,满足所有的K mod ai = ri。
这道题还是一道板题,但是题目要求正整数,所以不能是0,如果是0最后要加M。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const long long MAXN=1005;
long long n,m[MAXN],r[MAXN];
long long gcd(long long a,long long b)
{
    return a%b==0?b:gcd(b,a%b);
}
void exgcd(long long a,long long b,long long &x,long long &y)
{
    if(b==0)
    {
        x=1,y=0;
        return ;
    }
    long long xx,yy;
    exgcd(b,a%b,xx,yy);
    x=yy;
    y=xx-(a/b*yy);
}
int main()
{
    long long T,tt=0;
    scanf("%I64d",&T);
    while(T--)
    {
        scanf("%I64d",&n);
        for(long long i=1;i<=n;i++)
            scanf("%I64d",&m[i]);
        for(long long i=1;i<=n;i++)
            scanf("%I64d",&r[i]);
        long long R=r[1],M=m[1],d,c,k1,k2,key=0;
        for(long long i=2;i<=n;i++)
        {
            d=gcd(M,m[i]);
            c=r[i]-R;
            if(c%d)
            {
                key=1;
                break;
            }
            m[i]=m[i]/d;
            c=c/d;
            exgcd(M/d,m[i],k1,k2);
            k1=((c*k1)%m[i]+m[i])%m[i];
            R=R+k1*M;
            M=M*m[i];
            R=(R%M+M)%M;
        }
        tt++;
        printf("Case %I64d: ",tt);
        R=(R+M)%M;
        if(key==0)
        {
            if(R==0)
                R=M;
            printf("%I64d\n",R);
        }
        else
            printf("-1\n");
    }
}
五.[NOI2018 D2T1]屠龙勇士

首先,对于每一条龙,我们都可以知道我们要用哪一把剑,这个可以直接map查找完成,在确定了用哪一把剑后,我们在知道了龙的血量A[i],恢复力m[i],和我们的攻击力xs[i]后(字母乱打的,勿喷)。可以知道这样一个等式:A[i]-X * xs[i]=0(mod m[i]),移项得A[i]=X * xs[i](mod m[i])所以先同时约去公因数后求出xs[i]在mod m[i]意义下的逆元可得X=A[i]*(xs[i])^-1(mod m[i])这样就有n个方程了,用中国剩余定理解决即可。
下面是代码。

#include<cstdio>
#include<map>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const long long MAXN=100005,INF=9223372036854775807ll;
long long n,m;
long long A[MAXN],M[MAXN],get[MAXN],xs[MAXN];
map<long long,int>nowj;
long long ksc(long long x,long long y,long long MOD)
{
    long long re=0;
    if(y<0)
        x=-x,y=-y;
    while(y)
    {
        if(y&1)
            re=(re+x)%MOD;
        x=(x+x)%MOD;
        y>>=1;
    }
    return re;
}
long long gcd(long long a,long long b)
{
    return a%b==0?b:gcd(b,a%b);
}
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 q=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return q;
}
long long findny(long long a,long long m)
{
    long long x=0,y=0;
    exgcd(a,m,x,y);
    return x;
}
int main()
{
    //freopen("dragon.in","r",stdin);
    //freopen("dragon.out","w",stdout);
    long long T;
    scanf("%lld",&T);
    while(T--)
    {
        memset(A,0,sizeof(A));
        memset(M,0,sizeof(M));
        memset(get,0,sizeof(get));
        nowj.clear();
        long long mi=0;//保证砍mi刀所有的龙血量都小于等于0
        scanf("%lld %lld",&n,&m);
        for(long long i=1;i<=n;i++)
            scanf("%lld",&A[i]);
        for(long long i=1;i<=n;i++)
            scanf("%lld",&M[i]);
        for(long long i=1;i<=n;i++)
            scanf("%lld",&get[i]);
        for(long long i=1;i<=m;i++)
        {
            long long e;
            scanf("%lld",&e);
            nowj[e]++;
        }
        nowj[-1]++;//哨兵节点
        for(long long i=1;i<=n;i++)
        {
            map<long long,int>::iterator it;
            it=nowj.upper_bound(A[i]);
            it--;
            if(it->first==-1)
                it++;
            xs[i]=it->first;
            it->second--;
            if(it->second==0)
                nowj.erase(it);
            nowj[get[i]]++;
        }
        for(long long i=1;i<=n;i++)
        {
            int GG=gcd(gcd(A[i],M[i]),xs[i]);
            A[i]/=GG;
            M[i]/=GG;
            xs[i]/=GG;
            mi=max(((A[i]+xs[i]-1)/xs[i]),mi);
            long long ny=findny(xs[i],M[i]);
            A[i]=(ksc(A[i],ny,M[i])+M[i])%M[i];
            xs[i]=1;
        }
        long long key=0;
        for(long long i=1;i<n;i++)
        {
            long long a1=A[i],a2=A[i+1],n1=M[i],n2=M[i+1];
            long long c=(a2-a1)%n2,D=gcd(n1,n2);
            if(c%D!=0)
            {
                key=1;
                break;
            }
            long long ny=findny(n1/D,n2/D);
            long long K=(ksc(c/D,ny,(n2/D))+(n2/D))%(n2/D);
            long long zm=n1/D*n2;
            long long za=ksc(n1,K,zm)+a1;
            za=(za%zm+zm)%zm;
            A[i+1]=za;
            M[i+1]=zm;
        }
        if(!key)
        {
            long long X=A[n];
            X=(X%M[n]+M[n])%M[n];
            if(X<mi)
            {
                int cc=(mi-X+M[n]-1)/M[n];
                X+=cc*M[n];
            }
            printf("%lld\n",X);
        }
        else
            printf("-1\n");
    }
    //fclose(stdin);
    //fclose(stdout);
}
六.HDU1788:Chinese remainder theorem again

题意:给定数组Mi和数字a,求一个最小的数K,满足K mod mi = (mi - a)。
这道题很明显可以用中国剩余定理直接做,但是既然a都一样,会不会有什么巧妙的方法呢?
我们把每个式子合并一下看看。
x mod m[1] = -a;
x mod m[2] = -a;
根据这两个式子,存在两个整数k[1],k[2]:
x = m[1] * k[1] -a;
x = m[2] * k[2] -a;
由于两个方程的左边都是x,因此有:
m[1] * k[1] -a= m[2] * k[2] -a;
m[1] * k[1] - m[2] * k[2] = 0;

m[1] * k[1] =m[2] * k[2];
令 K=m[i]*k[i];
所以很明显x=K-a;
这样我们来看一下这个K,很明显K是所有m[i]的倍数,用于题目要求x最小,因此K为所有m的最小公倍数,最后答案是K-a

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
long long n,a;
long long gcd(long long a,long long b)
{
    return b==0?a:gcd(b,a%b);
}
int main()
{
    while(~scanf("%lld %lld",&n,&a)&&n&&a)
    {
        long long M=1,m=0;
        for(long long i=1;i<=n;i++)
        {
            scanf("%lld",&m);
            M=M/gcd(M,m)*m;
        }
        printf("%lld\n",M-a);
    }
}
七.HDU3430:Shuffling

题意:给定一种重排规则A[N],A[i]表示将第A[i]个数放到第i个位置(1<=i<=N)。然后给定一个目标状态,问从起始状态到达目标状态,最少需要经过多少步。
想用置换群?只用置换群一步步的变上去是会T的,在算出起始状态和目标的置换群后先看一下是否一样。如果不一样,直接-1,如果一样,就可以列方程了,比如(12)(345)(6)到(21)(534)(6)可得
x=1(mod2)
x=2(mod 3)
x=0(mod1)
然后用中国剩余定理解出答案即可

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const long long MAXN=10005;
long long n;
long long z[MAXN],a[MAXN],b[MAXN],ca[MAXN],cb[MAXN];
long long m[MAXN],r[MAXN];
long long vis[MAXN];
long long gcd(long long a,long long b)
{
    return a%b==0?b:gcd(b,a%b);
}
void exgcd(long long a,long long b,long long &x,long long &y)
{
    if(b==0)
    {
        x=1,y=0;
        return ;
    }
    long long xx,yy;
    exgcd(b,a%b,xx,yy);
    x=yy;
    y=xx-(a/b*yy);
}
void dfs(long long *A,long long *B,long long now)
{
    if(A[B[now]])
        return ;
    A[B[now]]=B[a[now]];
    dfs(A,B,a[now]);
}
void dfs1(long long now,long long id,long long dep,long long s)
{
    if(vis[now])
    {
        m[id]=dep;
        return ;
    }
    vis[now]=1;
    if(b[now]==s)
        r[id]=dep;
    dfs1(a[now],id,dep+1,s);
}
int main()
{
    while(~scanf("%lld",&n)&&n)
    {
        memset(vis,0,sizeof(vis));
        memset(ca,0,sizeof(ca));
        memset(cb,0,sizeof(cb));
        memset(m,0,sizeof(m));
        memset(r,0,sizeof(r));
        for(long long i=1;i<=n;i++)
        {
            long long u;
            scanf("%lld",&u);
            a[u]=i;
        }
        for(long long i=1;i<=n;i++)
        {
            scanf("%lld",&b[i]);
            z[i]=i;
        }
        for(long long i=1;i<=n;i++)
        {
            dfs(ca,z,i);
            dfs(cb,b,i);
        }
        long long key=0;
        for(long long i=1;i<=n;i++)
        {
            if(ca[i]!=cb[i])
            {
                printf("-1\n");
                key=1;
                break;
            }
        }
        if(key)
            continue;
        long long tot=0;
        for(long long i=1;i<=n;i++)
        {
            if(!vis[i])
            {
                ++tot;
                dfs1(i,tot,0,i);
            }
        }
        long long R=r[1],M=m[1],d,c,k1,k2;
        key=0;
        for(long long i=2;i<=tot;i++)
        {
            d=gcd(M,m[i]);
            c=r[i]-R;
            if(c%d)
            {
                printf("-1\n");
                key=1;
                break;
            }
            exgcd(M/d,m[i]/d,k1,k2);
            k1=(c/d*k1)%(m[i]/d);
            R=R+k1*M;
            M=M/d*m[i];
            R%=M;
        }
        if(key)
            continue;
        if(R<0)
            R=R%M+M;
        printf("%lld\n",R);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值