[R34C]无限序列
数据规模:,,。
思路
序列 由每个正整数 的 进制串 与其反转 拼接得到,即每项贡献 ,共 位。把 按其 进制位数 分段处理,可在 时间内定位到第 位。
按位数分段。 位数的 取值范围为 ,共有 个。每个 贡献 位,所以整个 位段的总位数为
从 起依次累减,把 减去各段总位数,直到 ,即定位到所在的位数 。
段内定位。 在 位段内每个 占 位。设 ,则
- 该 在段内排第 ( 开始),对应的正整数为 ;
- 在 中处于第 位( 开始)。
记 的 位(高位在前)为 ,则 。因此第 位为:
求 的 进制表示。 反复对 取余、整除得到低位在前的各位,填入长度为 的数组(高位在前)即可。
防溢出。 , 时 ,故 。累减和幂运算全程使用 Int64,足够安全。
复杂度
每次询问时间 ,即位数 的量级( 时至多约 )。空间 。 时总耗时远低于限制。
仓颉实现
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
}