[R83F] 构造全球化


数据规模:1≤m≤2×1051 \le m \le 2 \times 10^5,1≤∣A∣≤2×1051 \le |A| \le 2 \times 10^5,AA 的首位为 11 至 99,其余各位为 00 至 99。

思路

把要求写成两条同余式。 设 ∣A∣=la|A| = l_a,名 BB 的长度为 LL。姓在前得到的整数是 A⋅10L+BA \cdot 10^L + B,名在前得到的是 B⋅10la+AB \cdot 10^{l_a} + A,所以

B≡r1=−A⋅10L(modm),B⋅10la≡−A(modm).B \equiv r_1 = -A \cdot 10^L \pmod m, \qquad B \cdot 10^{l_a} \equiv -A \pmod m.

关键观察:第二条只由 B mod mB \bmod m 决定。 两条都是关于 BB 的模 mm 同余式,而第一条已经把 B mod mB \bmod m 完全钉死为 r1r_1。因此长度 LL 可行,当且仅当「把 r1r_1 代入第二条成立」并且区间 [10L−1,10L)[10^{L-1}, 10^L) 内确实存在模 mm 余 r1r_1 的数。下面把「代入后成立」化成好算的判据。

分解出 gg 再整体除。 令 g=gcd⁡(10la,m)g = \gcd(10^{l_a}, m)。由于 10la10^{l_a} 只有质因子 2,52, 5,

g=2min⁡(la, v2(m))⋅5min⁡(la, v5(m)),g = 2^{\min(l_a,\, v_2(m))} \cdot 5^{\min(l_a,\, v_5(m))},

把 mm 反复除以 22、55 就能得到(v2(m)≤17v_2(m) \le 17,v5(m)≤7v_5(m) \le 7,故 g≤mg \le m)。

  • 若 g∤Ag \nmid A:因为 g∣10lag \mid 10^{l_a} 且 g∣mg \mid m,任何 BB 都使 B⋅10la+A≢0(modm)B \cdot 10^{l_a} + A \not\equiv 0 \pmod m,直接输出 NO。
  • 若 g∣Ag \mid A:两边同除以 gg,得 B⋅10lag≡−Ag(modm2)B \cdot \frac{10^{l_a}}{g} \equiv -\frac{A}{g} \pmod{m_2},其中 m2=m/gm_2 = m/g。此时 10lag\frac{10^{l_a}}{g} 与 m2m_2 互质:v2(10lag)=max⁡(0,la−v2(m))v_2\left(\frac{10^{l_a}}{g}\right) = \max(0, l_a - v_2(m)),而 v2(m2)=max⁡(0,v2(m)−la)v_2(m_2) = \max(0, v_2(m) - l_a),两者不会同时为正,55 的情形同理。求逆元即得

B≡r2=−Ag⋅(10lag)−1(modm2).B \equiv r_2 = -\frac{A}{g} \cdot \left(\frac{10^{l_a}}{g}\right)^{-1} \pmod{m_2}.

m2=1m_2 = 1 时第二条恒成立,取 r2=0r_2 = 0。求 A/g mod m2A/g \bmod m_2 不需要大整数除法:g∣Ag \mid A 且 g∣mg \mid m,于是 (A mod m)/g=(A/g) mod m2(A \bmod m)/g = (A/g) \bmod m_2。计算 10lag mod m2\frac{10^{l_a}}{g} \bmod m_2 时按 2la−min⁡(la,v2(m))⋅5la−min⁡(la,v5(m))2^{l_a - \min(l_a, v_2(m))} \cdot 5^{l_a - \min(l_a, v_5(m))} 快速幂即可,不必真除。

合并两条同余,依据是 m2∣mm_2 \mid m。 若 r1 mod m2≠r2r_1 \bmod m_2 \ne r_2,则 B≡r1(modm)B \equiv r_1 \pmod m 的任何解代回第二条都不成立,这个长度作废;反之若 r1 mod m2=r2r_1 \bmod m_2 = r_2,则所有 B≡r1(modm)B \equiv r_1 \pmod m 都自动满足第二条(它们都 ≡r1(modm2)\equiv r_1 \pmod{m_2}),可行解集恰好就是一条模 mm 的同余类。

固定长度取最小解。 类 r1 mod mr_1 \bmod m 中不小于 10L−110^{L-1} 的最小数是 B0=10L−1+δB_0 = 10^{L-1} + \delta,其中 δ=(r1−10L−1) mod m∈[0,m)\delta = (r_1 - 10^{L-1}) \bmod m \in [0, m)。B0B_0 是 LL 位数当且仅当 δ<9⋅10L−1\delta < 9 \cdot 10^{L-1};因为 δ<m≤2×105\delta < m \le 2 \times 10^5,只要 L≥8L \ge 8 就必然成立,只有 L≤7L \le 7 需要真的比较一次。B0B_0 同时是长度为 LL 的最小可行解,所以按 LL 从小到大扫,第一次成功即为答案。

长度上界。 r1 mod m2=(−A)⋅10L mod m2r_1 \bmod m_2 = (-A) \cdot 10^L \bmod m_2 只取决于 10L mod m210^L \bmod m_2。当 LL 不小于 max⁡(v2(m2),v5(m2))\max(v_2(m_2), v_5(m_2)) 时(这个阈值不会超过 1818),10L10^L 对 m2m_2 的 22、55 部分都是 00,剩下的取值由 10L10^L 对 oo 取模决定,其中 oo 是 m2m_2 去掉 2,52, 5 因子后的部分。1010 与 oo 互质,10L mod o10^L \bmod o 从 L=0L = 0 起就是纯周期的,周期不超过 φ(o)≤o≤m2\varphi(o) \le o \le m_2。因此只要存在合法长度,就存在一个不超过 m2+18m_2 + 18 的合法长度;扫到 L=m2+60L = m_2 + 60 仍未成功即可输出 NO。

复杂度

读入 AA 需要 O(∣A∣)O(|A|);检查长度需要 O(m2)≤O(m)O(m_2) \le O(m) 次常数运算;输出 O(L)≤O(m)O(L) \le O(m)。空间为 O(∣A∣+L)O(|A| + L),即读入的数字串与输出串。

仓颉实现

答案长度可达 2×1052 \times 10^5 位。因为 δ<107\delta < 10^7,当长度较大时直接按「1 + 若干个 0 + δ\delta 补足 77 位」拼出答案,不会产生进位。

查看仓颉实现
import std.env.*

func readNum(s: String): Int64 {
    var x: Int64 = 0
    for (ch in s) {
        let c = Int64(ch)
        if (c > 32) {
            x = x * 10 + (c - 48)
        }
    }
    return x
}

func powMod(b: Int64, e: Int64, md: Int64): Int64 {
    var res: Int64 = 1 % md
    var base: Int64 = b % md
    var ex = e
    while (ex > 0) {
        if (ex % 2 == 1) {
            res = res * base % md
        }
        base = base * base % md
        ex /= 2
    }
    return res
}

func modInv(a: Int64, md: Int64): Int64 {
    var r0 = md
    var r1 = a % md
    var t0: Int64 = 0
    var t1: Int64 = 1
    while (r1 != 0) {
        let q = r0 / r1
        let nr = r0 - q * r1
        r0 = r1
        r1 = nr
        let nt = t0 - q * t1
        t0 = t1
        t1 = nt
    }
    var res = t0 % md
    if (res < 0) {
        res += md
    }
    return res
}

main() {
    let reader = getStdIn()
    let m = readNum(reader.readln().getOrThrow())
    let sa = reader.readln().getOrThrow()
    var am: Int64 = 0
    var la: Int64 = 0
    for (ch in sa) {
        let c = Int64(ch)
        if (c > 32) {
            am = (am * 10 + (c - 48)) % m
            la += 1
        }
    }
    var v2: Int64 = 0
    var t = m
    while (t % 2 == 0) {
        t /= 2
        v2 += 1
    }
    var v5: Int64 = 0
    t = m
    while (t % 5 == 0) {
        t /= 5
        v5 += 1
    }
    var e2 = la
    if (v2 < e2) {
        e2 = v2
    }
    var e5 = la
    if (v5 < e5) {
        e5 = v5
    }
    var g: Int64 = 1
    var i: Int64 = 0
    while (i < e2) {
        g *= 2
        i += 1
    }
    i = 0
    while (i < e5) {
        g *= 5
        i += 1
    }
    if (am % g != 0) {
        println("NO")
    } else {
        let m2 = m / g
        var r2: Int64 = 0
        if (m2 > 1) {
            let q = powMod(2, la - e2, m2) * powMod(5, la - e5, m2) % m2
            let inv = modInv(q, m2)
            let ag = am / g % m2
            r2 = (m2 - ag * inv % m2) % m2
        }
        var pm: Int64 = 1 % m
        var p: Int64 = 10 % m
        var t10: Int64 = 1
        var len: Int64 = 1
        let limit = m2 + 60
        var found = false
        var delta: Int64 = 0
        while (len <= limit && !found) {
            let r1 = (m - am * p % m) % m
            if (r1 % m2 == r2) {
                var d = (r1 - pm) % m
                if (d < 0) {
                    d += m
                }
                if (len > 7 || t10 + d < t10 * 10) {
                    found = true
                    delta = d
                }
            }
            if (!found) {
                pm = pm * 10 % m
                p = p * 10 % m
                if (t10 <= 100000000000000000) {
                    t10 *= 10
                }
                len += 1
            }
        }
        if (!found) {
            println("NO")
        } else if (len <= 18) {
            println(t10 + delta)
        } else {
            let sb = StringBuilder()
            sb.append("1")
            var z: Int64 = 0
            while (z < len - 8) {
                sb.append("0")
                z += 1
            }
            let ds = delta.toString()
            z = 0
            while (z < 7 - ds.size) {
                sb.append("0")
                z += 1
            }
            sb.append(ds)
            println(sb.toString())
        }
    }
}