[R36D]字符串序列
题意
定义字符串序列 ,给定 ,对 :
其中 把每个字符替换成字母表中的下一个字符(z 变 a)。
给定 组询问,每组给出 与正整数 ,求 的第 个字符(下标从 开始)。,,。
思路
长到无法显式构造,但其结构是高度递归的,直接分治定位即可。
记 ,则 。由于每层长度翻倍, 增长极快,约 层就超过 。因此对每组询问,先从小到大预处理长度序列,直到某层长度 ,设这层为第 层(任意更高层都会在递归下降时被跳过,所以取恰好够用的那层即可)。
第 层的结构为:
设 (中间那个 a 的位置)。对查询位置 :
- 若 ,答案就是
a,再累计上累计的位移即可。 - 若 ,落点在左半,等价于查询第 层的第 位,递归。
- 若 ,落点在右半 ,等价于查询第 层的第 位,但最终结果需要再 next 一次。
关键观察:每往右半走一次,最终取到的 中的字符都要整体 next 一次(z 后绕回 a)。因此只需维护一个位移计数 ,每次进入右半就 。最后若落到 的某个字符 ,答案是 ;若中途命中 a,答案是 。
整个下降过程每层最多走一次,深度约 60,单次查询 ,总复杂度 。
注意 增长时要设一个上界(如 )避免 Int64 溢出。
代码
import std.console.*
import std.convert.*
import std.collection.*
main(): Int64 {
let reader = Console.stdIn
let t = Int64.parse(reader.readln().getOrThrow())
let limit = Int64(2000000000000000000) // 2e18,防止 len 预处理溢出
var sb = StringBuilder()
for (_ in 0..t) {
let parts = reader.readln().getOrThrow().split(" ", removeEmpty: true)
let l = Int64.parse(parts[0])
let k = Int64.parse(parts[1])
let s0 = reader.readln().getOrThrow().toRuneArray()
// 预处理每层长度,直到 >= k
var lenList = ArrayList<Int64>()
var cur = l
lenList.add(cur)
while (cur < k && cur < limit) {
cur = cur * 2 + 1
lenList.add(cur)
}
// 从最深层开始递归下降
var level = Int64(lenList.size - 1)
var kk = k
var shift = Int64(0)
var done = false
while (level > 0) {
let prevLen = lenList[level - 1]
let mid = prevLen + 1
if (kk == mid) {
let c = shift % 26 + 97
sb.append(Rune(UInt32(c)))
done = true
break
} else if (kk < mid) {
level = level - 1
} else {
kk = kk - mid
shift = shift + 1
level = level - 1
}
}
if (!done) {
// level == 0,落到 s_0[kk-1]
let base = Int64(UInt32(s0[kk - 1])) - 97
let c = (base + shift) % 26 + 97
sb.append(Rune(UInt32(c)))
}
sb.append("\n")
}
print(sb.toString())
return 0
}