1、将式子等价转换
考虑前两个式子:
(x≡m_1(\% a_1))
(x≡m_2(\% a_2))
变形:
(x=k_1∗a_1+m_1)
(x=k_2∗a_2+m_2)
联立消掉(x),得到:
(k_1∗a_1+m_1=k_2∗a_2+m_2)
移项:
(k_1∗a_1−k_2∗a_2=m_2−m_1)
也就是:
(① k_1∗a_1+k_2∗(−a_2)=m_2−m_1)
本题预求:一个最小的非负整数(x)
因为要求(x)最小,而(a_i)和(m_i)都是正数,也就是需要找到一个最小的(k_1,k_2),使得等式成立。
那么如何找到(k_1,k_2)的最小解呢?方程(①)其实就是形如(ax+by=c)的一个不定方程,可以考虑使用扩展欧几里得来求最小解。
扩展欧几里得来求最小解的步骤:
-
利用扩展欧几里得计算出方程(ax+by=gcd(a,b))的一组解(x_0,y_0)。
-
通过变换公式得到(ax+by=c)的特解(x_1=x_0∗c/d, y_1=y_0∗c/d;)
-
最小解:(int s=b/d; x_{min}=(x_1\%s+s)\%s);
2. 用扩展欧几里得算法找出一组解
已知(a_1,m_1,a_2,m_2),可以用扩展欧几里得算法算出一个(k′_1,k′_2)使得:
(② k′_1∗a_1+k′_2∗(−a_2)=gcd(a_1,−a_2)) 【这是在计算(ax+by=gcd(a,b))的一组解】
无解判断:
若(gcd(a_1,−a_2)∤(m_2−m_1)),则无解,返回即可。
如果有解:
设(d=gcd(a_1,−a_2),y=frac{m_2−m_1}{d})
连立(①)式和(②)式:
我们只需要将(k′_1)和(k′_2)分别扩大(y)倍,则可以以找出一个(k_1,k_2)满足(①)式:
(k_1=k′_1∗y,k_2=k′_2∗y) 【这是在求(ax+by=c)的特解】
3. 找到最小正整数解
有了(ax+by=c)的特解,就可以来求方程的通解和最小解了。
我们知道一个性质:
(③ k_1=k_1+k∗frac{a_2}{d})
( k_2=k_2+k∗frac{a_1}{d})
(k)为任意整数,这时新的(k_1,k_2)仍满足(①)式。
性质证明:
将新的(k_1,k_2)带入式子(k_1∗a_1−k_2∗a_2=m_2−m_1)得:
((k_1+k∗frac{a_2}{d})∗a_1+(k_2+k∗frac{a_1}{d})∗(−a_2)=m_2−m_1)
拆出来:
(k_1∗a_1+k∗frac{a_2∗a_1}{d}+k_2∗(−a_2)+k∗frac{a_1∗(−a_2)}{d}=m_2−m_1)
交换一下顺序,把负号拆出来:
(k_1∗a_1+k_2∗(−a_2)+k∗frac{a_2∗a_1}{d}−k∗frac{a_1∗a_2}{d}=m_2−m_1)
那个同加同减可以消掉:
(k_1∗a_1+k_2∗(−a_2)=m_2−m_1)
这个式子和(①)是一样的,因(①)成立,故此式也成立。
有了上面的性质,要找一个最小的非负整数解,我们只需要让
(k_1=k_1 \% abs(frac{a_2}{d}))
(k_2=k_2 \% abs(frac{a_1}{d}))
就是在(k_1)中尽可能的去掉(abs (frac{a_2}{d})),就可找到当前最小的(k_1,k_2)的解,也就是不断的减小(k)的值,直至(k)为(0),简单点来说是,就是一步模完。
(Q):此处为什么要取绝对值呢?
(A):因为不知道(frac{a_2}{d})的正负性,我们在原基础上要尽量减多个(abs(frac{a_2}{d})),使其为正整数且最小。
4. 等效替代
由(②)式带入(x=k_1∗a_1+m_1)
则:
(x=(k_1+k∗frac{a_2}{d})∗a_1+m_1)
(=k_1∗a_1+m_1+k∗frac{a_2∗a_1}{d})
(=k_1∗a_1+m_1+k∗lcm(a_1,a_2)) ③
(Q):这里,(k)都为(0)了,为什么还要算呢?
(A):因为这只是前两个式子得最小(k),有可能遇到下一个式子后面被迫要扩大
在(③)中,我们设(a_0=lcm(a_1,a_2)),(m_0=k_1∗a_1+m_1) 【之所以这样设,是因为(a_1,a_2,k_1,m_1)都是已知数,可以算出来,未知的是(x,k)】
那么:
(③ =k∗a_0+m_0)
这个形式与一开始我们分解的形式是不是特别像呢?
没错!假设之后又来了一个(a3,m3)
我们只需要继续找:
(x=k∗a_0+m_0=k_3∗(−a_3)+m_3),那么问题又回到了第一步。
5.总结
我们的做法相当于每次考虑合并两个式子,将这(n)个式子合并(n−1)次后变为一个式子。最后剩下的式子就满足我们的答案。
注意:
-
(lcm(a_1,a_2))和(\%frac{a_2}{d}),需要取绝对值。又因为(d=gcd(a_1,−a_2)),我们不知道(a_1)的正负性(可能是上一步推过来的)。
-
(\%frac{a_2}{d}),需要取绝对值, 模负数的话,不会取到正解;
6、完整代码
#include <iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y) {
if (!b) {
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main() {
int n;
cin >> n;
//先读入第一个方程
LL a1, m1;
cin >> a1 >> m1;
//依次读入n-1个方程,每次合并一个方程
for (int i = 0; i < n - 1; i++) {
LL a2, m2;
cin >> a2 >> m2;
//开始合并
LL k1, k2;
//a1*k1+(-a2)*k2=m2-m1
LL d = exgcd(a1, -a2, k1, k2);
if ((m2 - m1) % d) { //如果不是0,则无解
cout << -1;
exit(0);
}
//求 ax+by=c的特解
k1 *= (m2 - m1) / d;
//使得k1成为方程的最小正整数解
int t = abs(a2 / d);
k1 = (k1 % t + t) % t;
//需要更新a1和m1了
m1 = k1 * a1 + m1;
a1 = abs(a1 / d * a2);
}
cout << (m1 % a1 + a1) % a1;
return 0;
}