预备知识:
(1)
(2)存在两个数:和,设是(和的最大公约数),则有:
(是和的最小公倍数)
(3)如果一个数可能的数值为,那么这个数的最小正整数解可以表示为
(4)在计算机中,负数模上负数的结果仍然是负数:
如
整数解(算法原理不是这个):
已知,,,...,两两互质(有解的前提条件),求解下列方程组:
设
设(是中的任意一个),就是除外所有项的乘积
因为 ,,,...,两两互质,所以和互质,那么运用逆元的概念:
必定存在一个满足,这个就是模意义下的乘法逆元,一般记作:
公式解(不要问公式怎么推的):
实例:
对于第一个方程:,将这个方程翻译一下,就是:
,将的公式解代入到方程中,那么就是:
因为根据逆元的定义:,将其代入至上面的公式中:
得到:
对于括号中的第二项:因为是除外的所有数的乘积,所以一定是0,对于其它项,也是一样的道理,因此计算过后上式就变为:
(这是化简之后的式子,发现正好方程组的第一个方程成立)
再以同样的方式将的公式解代入至方程组的其他方程中,发现均符合,所以的公式解就是:
如何求逆元?
(之前学的快速幂求逆元要求模上的数必须是质数,才可以用快速幂,但此时模上的数不一定是质数,因此需要用裴蜀定理和扩展欧几里得算法):
将看成,有公式:,将公式进行变形得到:
(其中的是任意一个整数,当和互质时必定存在),设,得到:
,因为和互质,所以,所以,这就是裴蜀定理的公式,采用扩展欧几里得算法进行求解
算法原理:
假设此时方程组中只有两个方程
将他们变形为:
可得,等式变换一下,得到
发现这是线性同余方程的形式(线性同余方程:http://t.csdnimg.cn/1itN4http://t.csdnimg.cn/1itN4http://t.csdnimg.cn/1itN4)
因此只要,那么就一定可以求出一组,使得等式成立
求出特解后,可以由此推算出的所有解的形式:
和的所有解的形式:
(其中:是和的最大公约数;是任意一个整数)
那么的所有解就可以表示为:(用表示)
(用表示)
因为,所以两种表示形式下,是相等的。
因为的所有解为,我们想要取得的最小正整数解,用这样的方式:
ll t = abs(m_2/d);
k_1 = (k_1%t + t)%t;
就可以得到的最小正整数解。
那么,这个就是开始方程组的解。
根据的所有解的形式,得到:
发现得到的等式的形式和一开始方程组中的方程形式类似,因此我们将第一个方程中的更新为,更新为
这样的话,我们就将一开始方程组中的两个方程合并为了一个方程,并且用这个合并之后的方程替代了原本方程组中的第一个方程,如果方程组中还有第三个方程,就用这个新的第一个方程和第三个方程合并,再次得到一个新的方程,接下来就以此类推,直到方程组中没有剩下的方程了。
那么最后得到的,就一定是方程组中所有方程的解,但不一定是最小的
所以还要求的最小正整数解:
所以将,那么,所以求的最小正整数解就是求的最小正整数解,根据预备知识(3)所以的最小正整数解可以表示为:
step1:
在循环外读入和,因为除了第一次循环和会作为原始方程组中的第一个方程中的数,之后的循环中,和是作为之前所有方程的合并方程的数
(和起到了记录合并方程的作用)
step2:
开始循环
读入新的方程中的数和,结合第一个方程,得到一个线性同余方程(http://t.csdnimg.cn/1itN4),利用扩展欧几里得算法求出和的最大公约数。
如果不能整除,说明这个线性同余方程无解,那么读入的两个方程就没有共同的解,推出循环
如果能够整除,说明这个线性同余方程一定有解。
(但是因为扩展欧几里得算法求出的满足的是,所以要想得到满足,要将求出的更新为)
step3:
根据上一个板块 算法原理 我们知道了的所有解的形式为,因为题目要求的是的最小整数解,所以要求出所有解中的最小正整数解:
(注意:是的绝对值形式,如果取到负值的话,是无法取得最小正整数)
step4:
将更新之后的代入至第一个方程中(或者第二个方程),,得到的就是初始两个方程的共同解
step5:
将更新为,将更新为,这样就将方程组中的第一个方程的数更新为了原来的第一个方程和第二个方程的合并方程的数。
继续循环下去,不断将和更新为循环读入的所有方程的合并方程中的数。
step6:
直到循环结束,那么此时和就是方程组中所有方程的合并方程的中的两个数
那么最后一次循环中的就是所有方程合并方程的解,自然就是整个方程组的解
step7:
为了确保是所有解中的最小正整数解,将,那么,所以求的最小正整数解就是求的最小正整数解,根据预备知识(3)所以的最小正整数解可以表示为:
题目如下:
给定 2n 个整数 a1,a2,…,an和 m1,m2,…,mn,求一个最小的非负整数 x,满足 ∀i∈[1,n],x≡ai(mod mi)
输入格式
第 1 行包含整数 n。
第 2…n+1 行:每 i+1 行包含两个整数 mi 和 ai,数之间用空格隔开。
输出格式
输出最小非负整数 x,如果 x 不存在,则输出 −1。
数据范围
0≤mi<ai
1≤n≤25
所有 mi 的最小公倍数在 64 位有符号整数范围内。
代码如下:
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
int n;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
ll d = exgcd(b,a%b,x,y);
ll tmp = x;
x =y;
y = tmp-a/b*y;
return d;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
ll a_1,m_1;
cin >>m_1 >> a_1;
ll x = 0;
for (int i = 0;i<n-1;++i)
{
ll m_2,a_2;
cin >> m_2 >> a_2;
ll k_1,k_2;
ll d = exgcd(m_1,-m_2,k_1,k_2);
if ((a_2-a_1)%d != 0)
{
x = -1;
break;
}
k_1 = k_1*(a_2-a_1)/d;
ll t = abs(m_2/d);
k_1 = (k_1%t+t)%t;
x = k_1*m_1+a_1;
a_1 = k_1*m_1+a_1;
m_1 = abs(m_1/d*m_2);
}
if (x !=-1)
x = (a_1%m_1+m_1)%m_1;
cout << x;
return 0;
}
(1)为什么需要 ll t = abs(m_2/d) 这一步
因为题目要求的是求的最小正整数解,而的解的形式为,所以要让取得尽可能小,也就是取得的最小正整数解,而的所有解的形式为,那么就通过(其中:)的方式,得到的最小正整数解。
(对于这个求最小正整数解的公式,简单模拟一下就明白过程和为什么需要了
需要用到预备知识(4))
(2)为什么要更新为要加abs
不会,背下来就完了。
但可以明确的是有没有 abs 对结果不会产生影响:
因为合并之后的方程的形式为:,我们将更新为,将更新为
如果,那么这个abs加不加都无所谓
如果,那么加了这个abs后,更新之后的值就会变为,和不加abs的更新值,相差了,而合并方程中的是一个任意整数,因此可以适应更新之后的值的不同。
(3)为什么最后要
因为最后的解的形式为,像要求得的最小正整数解,就要让,那么,所以求的最小正整数解就是求的最小正整数解,而的所有可能的解为,
所以的最小正整数解为
(4) 为什么m_1 = abs(m_1/d*m_2),不能是m_1 = abs((m_2*m_1)/d)
因为 m_2*m_1 的数可能很大,会爆掉 long long ,如果先除的话,就不会出现爆 long long 的情况。