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;
}