一元线性方程组求解
什么是一元线性方程组呢?就是由若干个形如x=b1(mod a1),x=b1(mod a2)…组成,之所以称之为一元,是因为只有一个未知数x
例如方程组
x=b1(mod a1)
x=b2(mod a2)
x=b3(mod a3)
x=b4(mod a4)
.
.
.
.
x=bn(mod an)
如何求x呢?这里就需要用到合并操作,不断把两个方程合并成为一个,最后只剩下一个方程就OK
怎么个合并发呢?下面我们来推到两个方程的合并过程
x=b1(mod a1)
x=b2(mod a2)
则x=b1+a1y1 -----------1
x=b2+a2y2 ---------2
b1+a1y1 = b2+a2y2
a1y1-a2y2 = b2-b1
令d = gcd(a1,a2)
a1y1/d - a2y2/d = (b2-b1)/d
a1y1/d = (b2-b1)/d + a2y2/d
a1y1/d = (b2-b1)/d(mod a2/d)
a1y1 = b2-b1(mod a2/d)
y1=(b2-b1)/a1(mod a2/d) ------5
所以根据5式来看,一定存在一个最小的正整数y’,使得y1=y’(mod a2/d)
所以令y1=y’+(a2/d)C,将y1带入1式,得到x=b1+a1y’+a1a2/d
所以x=b1+a1y’(mod a1a2/d)<=> x=b’(mod a’)
所以合并后的b’=b1+a1y’,a’=a1*a2/d,注意这里有先后顺序,先处理b’,再处理a’,这里的y’根据a1y1-a2y2=b2-b1,用扩展欧几里得算法求得即可
然后不断这样合并下去,最后得到一个最终的方程,这个方程的解就是方程组的解
代码模板:
void exgcd(ll &x,ll &y,ll &g,ll a,ll b)
{
if(b==0)
{
g=a;
x=1;
y=0;
return ;
}
else
{
ll x1,y1;
exgcd(x,y,g,b,a%b);
x1=y;
y1=x-(a/b)*y;
x=x1;
y=y1;
}
return ;
}
ll solve(ll n)
{
int flag=1;
ll a1,r1;
cin>>a1>>r1;
for(ll i=2;i<=n;i++)
{
ll a2,r2,y1,y2,g;
cin>>a2>>r2;
ll c=r2-r1;
exgcd(y1,y2,g,a1,a2);
y1=y1*c/g;
ll s=a2/g;
y1=(y1%s+s)%s;//这里为了防止y1出现负数的情况
if(c%g)
{
flag=0;
}
r1=r1+y1*a1;
a1=a1*a2/g;
}
if(flag==0) return -1;//-1表示无解
return r1;
}
现在我们来趁热打铁
Strange Way to Express Integers
这是一道典型的解线性同余方程组的问题,
式子可以化为m=ri(mod ai) -> m=ri+aiyi,(1<=i<=k) ,合并k个方程即可
AC代码
#include<iostream>
using namespace std;
typedef long long ll;
void exgcd(ll &x,ll &y,ll &g,ll a,ll b)
{
if(b==0)
{
g=a;
x=1;
y=0;
return ;
}
else
{
ll x1,y1;
exgcd(x,y,g,b,a%b);
x1=y;
y1=x-(a/b)*y;
x=x1;
y=y1;
}
return ;
}
ll solve(ll n)
{
int flag=1;
ll a1,r1;
cin>>a1>>r1;
for(ll i=2;i<=n;i++)
{
ll a2,r2,y1,y2,g;
cin>>a2>>r2;
ll c=r2-r1;
exgcd(y1,y2,g,a1,a2);
y1=y1*c/g;
ll s=a2/g;
y1=(y1%s+s)%s;//这里为了防止y1出现负数的情况
if(c%g)
{
flag=0;
}
r1=r1+y1*a1;
a1=a1*a2/g;
}
if(flag==0) return -1;//-1表示无解
return r1;
}
int main()
{
ll n;
while(scanf("%lld",&n)!=EOF)
{
cout<<solve(n)<<endl;
}
return 0;
}
再来一道板子题,练手
Hello Kiki
这道题,不用多说,直接列出X=Ai(mod Mi),(1<=i<=N) 解这个方程组即可,但是要注意,题目要求输出最小的正整数解X,所以如果X解出来等于0,那么需要加上一个gcd(M1,M2,…,MN),也即合并后的M’
AC代码:
#include<iostream>
using namespace std;
typedef long long ll;
ll a[100],m[100];
void exgcd(ll &x,ll &y,ll &g,ll a,ll b)
{
if(b==0)
{
g=a;
x=1;
y=0;
return ;
}
else
{
ll x1,y1;
exgcd(x,y,g,b,a%b);
x1=y;
y1=x-(a/b)*y;
x=x1;
y=y1;
}
return ;
}
ll solve(ll n)
{
int flag=1;
ll a1=a[1],m1=m[1];
// cin>>a1>>m1;
for(ll i=2;i<=n;i++)
{
ll a2,m2,y1,y2,g;
a2=a[i],m2=m[i];
// cin>>a2>>r2;
ll c=a2-a1;
exgcd(y1,y2,g,m1,m2);
y1=y1*c/g;
ll s=m2/g;
y1=(y1%s+s)%s;//这里为了防止y1出现负数的情况
if(c%g)
{
flag=0;
}
a1=a1+y1*m1;
m1=m1*m2/g;
}
m[1]=m1;
if(flag==0) return -1;//-1表示无解
return a1;
}
int main()
{
int t;
cin>>t;
int kcase=0;
while(t--)
{
++kcase;
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>m[i];
for(int i=1;i<=n;i++) cin>>a[i];
int ans=solve(n);
cout<<"Case "<<kcase<<": ";
if(ans==-1)
{
cout<<"-1"<<endl;
}
else if(ans==0)
{
cout<<m[1]<<endl;
}
else cout<<ans<<endl;
}
return 0;
}
接一下来是一道稍微复杂一点的题目
X问题
这道题显然也是解一元线性方程组的问题,只不过多了一个求方程组的解小于等于N的非负整数解的个数条件,
假设方程组合并之后为x=r’(mod a’),这里的r’和a’都可以通过合并求得,然后得到x=r’+a’*C,这里的C是常数,那么我们只需要求0<=r’+a’*C<=N的常数C有多少个即可
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[20],b[20];
void exgcd(ll a,ll b,ll &g,ll &x,ll &y)
{
if(b==0)
{
x=1;
y=0;
g=a;
return ;
}
else
{
ll tempx,tempy;
exgcd(b,a%b,g,x,y);
tempx=y;
tempy=x-(a/b)*y;
x=tempx;
y=tempy;
return ;
}
}
ll solve(ll m)
{
int flag=1;
ll a1=a[1],b1=b[1];
for(ll i=2;i<=m;i++)
{
ll a2=a[i],b2=b[i];
ll c=b2-b1;
ll g,x,y;
exgcd(a1,a2,g,x,y);
if(c%g)
{
flag=0;
}
x=x*c/g;
ll s=a2/g;
x=(x%s+s)%s;
b1=b1+x*a1;
a1=a1*a2/g;
}
a[1]=a1;
if(flag==0) return -1;
return b1;
}
int main()
{
int t;
cin>>t;
while(t--){
ll n,m;
cin>>n>>m;
for(ll i=1;i<=m;i++) cin>>a[i];
for(ll i=1;i<=m;i++) cin>>b[i];
ll res=solve(m);
if(res==-1)
{
printf("0\n");
continue;
}
ll ans=0;
if(res>n)
{
printf("0\n");
}
else
{
if(res==0) ans=n/a[1];
else
{
ans=(n-res)/a[1]+1;
}
printf("%lld\n",ans);
}
}
return 0;
}