AcWing 204. 表达整数的奇怪方式

题目传送门

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)的一个不定方程,可以考虑使用扩展欧几里得来求最小解。

扩展欧几里得来求最小解的步骤:

  1. 利用扩展欧几里得计算出方程(ax+by=gcd(a,b))的一组解(x_0,y_0)

  2. 通过变换公式得到(ax+by=c)的特解(x_1=x_0∗c/d, y_1=y_0∗c/d;)

  3. 最小解:(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)次后变为一个式子。最后剩下的式子就满足我们的答案。

注意:

  1. (lcm(a_1,a_2))(\%frac{a_2}{d}),需要取绝对值。又因为(d=gcd(a_1,−a_2)),我们不知道(a_1)的正负性(可能是上一步推过来的)。

  2. (\%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;
}