特殊情况
我们先来看一下问题,现在有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);
}
}