[R14D]训练指令2
- 难度 普及/提高-
- 时限 2s
- 空限 512m
- 模拟置换
数据规模:,。 类指令保证 , 类指令保证 。
思路
如果真的维护 的方阵,每条 类指令就要搬动一整行或一整列,单次 ,总复杂度 必然超时。
关键观察:行的交换与列的交换互相独立。第 类指令只整行换,它决定一个同学最终位于「哪一行」,而完全不影响「哪一列」;第 类指令恰好相反。所以可以把行号和列号分开各维护一个置换。
具体地,令:
- 表示「现在位于第 行的同学,初始时位于第几行」,初值 ;
- 表示「现在位于第 列的同学,初始时位于第几列」,初值 。
每条指令只需 处理:
- :交换 与 ;
- :交换 与 ;
- :现在坐在 的同学,初始时坐在 ,编号为 ,直接输出。
为什么这样是对的?以行为例,一次「第 行与第 行整列交换」等价于把「当前在第 行的那批同学」和「当前在第 行的那批同学」整体对调,也就是把置换 中第 、 两个位置的值对调,列方向上完全不受影响。同理列交换只改 。于是行、列各自的置换在指令序列上独立累加,最后查询时再把行、列两端的初始位置合成就得到答案。
整体时间复杂度 ,空间复杂度 。 都到 时把两个置换数组用 UInt32 存储可以显著降低内存占用。
仓颉实现
import std.convert.*
import std.env.*
import std.collection.*
main() {
let reader = getStdIn()
let first = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let n = first[0]
let m = first[1]
let q = first[2]
// r[i] 表示现在位于第 i 行的同学初始时所在的行号;c[j] 同理表示列。
// 行的交换与列的交换互相独立,分别用置换数组维护即可。
let r = Array<UInt32>(n + 1, { i => UInt32(i) })
let c = Array<UInt32>(m + 1, { i => UInt32(i) })
let out = StringBuilder()
var i = 0
while (i < q) {
let line = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let op = line[0]
let x = line[1]
let y = line[2]
if (op == 1) {
let t = r[x]
r[x] = r[y]
r[y] = t
} else if (op == 2) {
let t = c[x]
c[x] = c[y]
c[y] = t
} else {
let ans = (Int64(r[x]) - 1) * m + Int64(c[y])
out.append(ans)
out.append("\n")
}
i = i + 1
}
print(out.toString())
}
要点:
- 「行交换只动行置换、列交换只动列置换」是本题的核心;想清楚这一点后,三 类询问就是一次 的合成,完全摆脱了方阵规模。
- 次输出逐条
print会产生大量 IO 开销,用StringBuilder收集后再一次性输出更稳妥。