解线性同余方程组

一元线性方程组求解

什么是一元线性方程组呢?就是由若干个形如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+a2
y2 ---------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+a1
y’(mod a1a2/d)<=> x=b’(mod a’)
所以合并后的b’=b1+a1
y’,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;
}

如果鄙人的博客能够让你有哪怕一点点的收获,都是鄙人最大的荣幸,鄙人厚着脸皮向您讨个点赞,谢谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值