【ACWing】204. 表达整数的奇怪方式

题目地址:

https://www.acwing.com/problem/content/206/

给定 2 n 2n 2n个整数 a 1 , . . . , a n a_1,...,a_n a1,...,an m 1 , . . . , m n m_1,...,m_n m1,...,mn,求一个最小的非负整数 x x x使得 ∀ i , x ≡ m i ( m o d    a i ) \forall i, x\equiv m_i(\mod a_i) i,xmi(modai)。如果无解就输出 − 1 -1 1

数据范围:
1 ≤ a i ≤ 2 31 − 1 1\le a_i\le 2^{31}-1 1ai2311
0 ≤ m i < a i 0\le m_i< a_i 0mi<ai
1 ≤ n ≤ 25 1\le n\le 25 1n25

考虑 n = 1 n=1 n=1的情形,即找到最小的非负整数满足: { x ≡ m 1 ( m o d    a 1 ) x ≡ m 2 ( m o d    a 2 ) \begin{cases} x\equiv m_1(\mod a_1)\\x\equiv m_2(\mod a_2) \end{cases} { xm1(moda1)xm2(moda2)即找到整数 k 1 , k 2 k_1,k_2 k1,k2使得: x = m 1 + k 1 a 1 = m 2 + k 2 a 2 x= m_1+k_1 a_1= m_2+k_2a_2 x=m1+k1a1=m2+k2a2变形得: k 1 a 1 − k 2 a 2 = m 2 − m 1 k_1a_1-k_2a_2=m_2-m_1 k1a1k2a2=m2m1我们可以先求解 l 1 a 1 − l 2 a 2 = gcd ⁡ ( a 1 , a 2 ) l_1a_1-l_2a_2=\gcd(a_1,a_2) l1a1l2a2=gcd(a1,a2),这个方程是一定有解的,可以用扩展欧几里得算法来求,思路和代码参考https://blog.csdn.net/qq_46105170/article/details/113824039。易知 k 1 a 1 − k 2 a 2 = m 2 − m 1 k_1a_1-k_2a_2=m_2-m_1 k1a1k2a2=m2m1有解当且仅当 gcd ⁡ ( a 1 , a 2 ) ∣ ( m 2 − m 1 ) \gcd(a_1,a_2)|(m_2-m_1) gcd(a1,a2)(m2m1)成立,如果这个式子不成立就可以直接输出 − 1 -1 1了,否则的话就有解。如果有解,令 d = gcd ⁡ ( a 1 , a 2 ) d=\gcd(a_1,a_2) d=gcd(a1,a2),设 ( k 1 , k 2 ) (k_1,k_2) (k1,k2)是一组解,那么 ∀ s , ( k 1 + s a 2 d , k 2 + s a 1 d ) \forall s,(k_1+s\frac{a_2}{d}, k_2+s\frac{a_1}{d}) s,(k1+sda2,k2+sda1)也是解,并且可以证明,任意的解都必然有这种形式(证明如下,如果 ( k 1 ′ , k 2 ′ ) (k_1',k_2') (k1,k2)也是解,那么有 ( k 1 − k 1 ′ ) a 1 d = ( k 2 − k 2 ′ ) a 2 d (k_1-k_1')\frac{a_1}{d}=(k_2-k_2')\frac{a_2}{d} (k1k1)da1=(k2k2)da2,由于 gcd ⁡ ( a 1 d , a 2 d ) = 1 \gcd(\frac{a_1}{d},\frac{a_2}{d})=1 gcd(da1,da2)=1,所以必须有 a 1 d ∣ ( k 2 − k 2 ′ ) \frac{a_1}{d}|(k_2-k_2') da1(k2k2),令 k 2 − k 2 ′ = − s a 1 d k_2-k_2'=-s\frac{a_1}{d} k2k2=sda1,那么有 k 1 ′ = k 1 + s a 2 d , k 2 ′ = k 2 + s a 1 d k_1'=k_1+s\frac{a_2}{d},k_2'=k_2+s\frac{a_1}{d} k1=k1+sda2,k2=k2+sda1)。只要求出 k 1 k_1 k1之后,就可以得出一个新的方程 x = ( k 1 + s a 2 d ) a 1 + m 1 = k 1 a 1 + m 1 + s a 1 a 2 d = ( k 1 a 1 + m 1 ) + s [ a 1 , a 2 ] x=(k_1+s\frac{a_2}{d})a_1+m_1=k_1a_1+m_1+s\frac{a_1a_2}{d}=(k_1a_1+m_1)+s[a_1, a_2] x=(k1+sda2)a1+m1=k1a1+m1+sda1a2=(k1a1+m1)+s[a1,a2](这里 [ a 1 , a 2 ] [a_1, a_2] [a1,a2] a 1 , a 2 a_1,a_2 a1,a2的最小公倍数),我们可以令 x 0 = ( k 1 a 1 + m 1 ) m o d    [ a 1 , a 2 ] x_0=(k_1a_1+m_1)\mod [a_1,a_2] x0=(k1a1+m1)mod[a1,a2],此时 x 0 x_0 x0就是最小非负的满足新方程的解,那么新方程等价于 x ≡ ( k 1 a 1 + m 1 ) ( m o d    [ a 1 , a 2 ] ) x\equiv (k_1a_1+m_1)(\mod [a_1,a_2]) x(k1a1+m1)(mod[a1,a2])。到此,我们就将原同余方程组的前两个方程合并为一个与它俩等价的方程,并且还求出了满足这个方程的最小非负整数解。进行 n − 1 n-1 n1次合并后,就能求出原题的最终解了。至于 k 1 k_1 k1怎么求,当求解出 l 1 a 1 − l 2 a 2 = d l_1a_1-l_2a_2=d l1a1l2a2=d之后,令 k 1 = l 1 m 2 − m 1 d k_1=l_1\frac{m_2-m_1}{d} k1=l1dm2m1即可。但是在代码中,为了防止溢出,我们需要让 k 1 k_1 k1尽可能小,那可以先求出任意一个 k 1 k_1 k1,然后取 k 1 k_1 k1 k 1 m o d    a 2 d k_1\mod \frac{a_2}{d} k1modda2即可,这样就可以让 k 1 a 1 + m 1 k_1a_1+m_1 k1a1+m1尽可能不溢出。令 m ′ = k 1 a 1 + m 1 m'=k_1a_1+m_1 m=k1a1+m1 a ′ = [ a 1 , a 2 ] a'=[a_1,a_2] a=[a1,a2],这样前两个方程合并的新方程就是 x ≡ m ′ ( m o d    a ′ ) x\equiv m'(\mod a') xm(moda)。合并 n − 1 n-1 n1次之后, m ′ m o d    a ′ m'\mod a' mmoda就是答案。代码如下:

#include <iostream>
using namespace std;

// 求ax + by = gcd(a, b)的一个解
long exgcd(long a, long b, long &x, long &y) {
    
    
    if (!b) {
    
    
        x = 1, y = 0;
        return a;
    }

    long d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    
    
    int n;
    cin >> n;
    
    bool has_answer = true;
    long a1, m1;

    cin >> a1 >> m1;
    for (int i = 0; i < n - 1; i++) {
    
    
        long a2, m2;
        cin >> a2 >> m2;

        long k1, k2;
        long d = exgcd(a1, a2, k1, k2);
        // 如果无解则直接退出
        if ((m1 - m2) % d) {
    
    
            has_answer = false;
            break;
        }

        k1 *= (m2 - m1) / d;
        long t = a2 / d;
        // 取最小非负的k1
        k1 = (k1 % t + t) % t;

        m1 += a1 * k1;
        a1 = abs(a1 / d * a2);
    }

    if (has_answer) cout << (m1 % a1 + a1) % a1 << endl;
    else cout << -1 << endl;

    return 0;
}

时间复杂度 O ( n log ⁡ max ⁡ a i ) O(n\log \max a_i) O(nlogmaxai),空间 O ( log ⁡ max ⁡ a i ) O(\log \max a_i) O(logmaxai)(这个空间是递归栈深度,但是由于long是有范围的,这个递归栈深度实际上不会超过 64 64 64,所以基本可以看成是常数)。

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/113934789