[R34C]无限序列


数据规模:1T1041 \le T \le 10^41n1091 \le n \le 10^92m102 \le m \le 10

思路

序列 g(m)g(m) 由每个正整数 kkmm 进制串 SS 与其反转 SrevS_{rev} 拼接得到,即每项贡献 S+SrevS + S_{rev},共 2S2|S| 位。把 kk 按其 mm 进制位数 dd 分段处理,可在 O(logmn)O(\log_m n) 时间内定位到第 nn 位。

按位数分段。 dd 位数的 kk 取值范围为 [md1,md1][m^{d-1},\, m^d - 1],共有 cntd=mdmd1\text{cnt}_d = m^d - m^{d-1} 个。每个 kk 贡献 2d2d 位,所以整个 dd 位段的总位数为

segd=(mdmd1)2d.\text{seg}_d = (m^d - m^{d-1}) \cdot 2d.

d=1d=1 起依次累减,把 nn 减去各段总位数,直到 nsegdn \le \text{seg}_d,即定位到所在的位数 dd

段内定位。dd 位段内每个 kk2d2d 位。设 width=2d\text{width} = 2d,则

  • kk 在段内排第 t=(n1)/widtht = \lfloor (n-1)/\text{width} \rfloor00 开始),对应的正整数为 k=md1+tk = m^{d-1} + t
  • S+SrevS + S_{rev} 中处于第 offset=(n1)modwidth\text{offset} = (n-1) \bmod \text{width} 位(00 开始)。

SSdd 位(高位在前)为 S[0..d1]S[0..d-1],则 Srev[i]=S[d1i]S_{rev}[i] = S[d-1-i]。因此第 offset\text{offset} 位为:

{S[offset],offset<dSrev[offsetd]=S[2d1offset],offsetd\begin{cases} S[\text{offset}], & \text{offset} < d \\ S_{rev}[\text{offset} - d] = S[2d - 1 - \text{offset}], & \text{offset} \ge d \end{cases}

kkmm 进制表示。 反复对 mm 取余、整除得到低位在前的各位,填入长度为 dd 的数组(高位在前)即可。

防溢出。 n109n \le 10^9m=2m=2230>1092^{30} > 10^9,故 d30d \le 30。累减和幂运算全程使用 Int64,足够安全。

复杂度

每次询问时间 O(logmn)O(\log_m n),即位数 dd 的量级(m=2m=2 时至多约 3030)。空间 O(d)O(d)T104T \le 10^4 时总耗时远低于限制。

仓颉实现

import std.console.*
import std.convert.*

func solve(reader: ConsoleReader): Unit {
    let line = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    var n = line[0]
    let m = line[1]
    // 找到 n 落在第几位的段:d 位数段
    var d: Int64 = 1
    var mpow: Int64 = 1 // m^{d-1}
    while (true) {
        let md = mpow * m // m^d
        let count = md - mpow // d 位数的个数
        let seg = count * 2 * d // 这一段总位数
        if (n <= seg) {
            break
        }
        n -= seg
        mpow = md
        d++
    }
    let dd = d
    let width = 2 * dd
    let t = (n - 1) / width // 段内第 t 个 d 位数(0-indexed)
    let offset = (n - 1) % width // 在 S+S_rev 中的 0-indexed 位置
    let k = mpow + t
    // k 在 m 进制下的 d 位表示(高位在前)
    let digits = Array<Int64>(dd, { _ => 0 })
    var i = dd - 1
    var kk = k
    while (i >= 0) {
        digits[i] = kk % m
        kk /= m
        i--
    }
    var idx: Int64
    if (offset < dd) {
        idx = offset
    } else {
        idx = 2 * dd - 1 - offset
    }
    println(digits[idx])
}

main(): Int64 {
    let reader = Console.stdIn
    let tt = Int64.parse(reader.readln().getOrThrow())
    var i: Int64 = 0
    while (i < tt) {
        solve(reader)
        i++
    }
    return 0
}