[R62C] 机器人巡逻
- 难度 普及
- 时限 1s
- 空限 512m
- 模拟差分
数据规模:,,。
思路
只需在最后输出每个格子的印章状态,不必关心中间过程。维护一个数组 ,记录每个格子被「翻转」的次数,最终 为奇数即有印章(输出 1),偶数即无印章(输出 0)。
机器人初始位于 、面朝右、工作状态为 。逐个操作模拟其位置、方向、状态:
- 操作
1 x:沿当前方向最多走 步,实际能走的步数受边界限制。面朝右时最多走 步,面朝左时最多走 步。注意 翻转的是新到达的格子,即 (向右)或 (向左),起始位置不被翻转。 - 操作
2:翻转方向。 - 操作
3:切换工作状态(状态 翻转、状态 只移动不翻转)。
难点在操作 1 处于状态 时,若暴力逐格 会退化到 。观察到一次操作翻转的是一段 连续区间:向右走 步翻转 ,向左走 步翻转 。区间加一、末尾单点查询是差分数组的经典用法:维护差分数组 ,对区间 加一只需 、,最后做一遍前缀和即得 数组。
时间复杂度 ,空间复杂度 。
仓颉实现
import std.env.*
import std.convert.*
main(): Int64 {
let reader = getStdIn()
let line = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let n = line[0]
let q = line[1]
var pos = line[2]
var dir = 1 // 1 = right, -1 = left
var state = 1 // 1 = toggle, 2 = idle
// Difference array of size n+2, 1-indexed.
let nn = n
var diff = Array<Int64>(nn + 2, { _ => 0 })
var i = 0
while (i < q) {
i += 1
let parts = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let op = parts[0]
if (op == 1) {
let x = parts[1]
if (state == 1) {
// Determine actual steps to take.
var steps = x
if (dir == 1) {
let maxSteps = n - pos
if (steps > maxSteps) {
steps = maxSteps
}
if (steps > 0) {
// Toggle positions [pos+1, pos+steps].
let l = pos + 1
let r = pos + steps
diff[l] += 1
diff[r + 1] -= 1
pos = r
}
} else {
let maxSteps = pos - 1
if (steps > maxSteps) {
steps = maxSteps
}
if (steps > 0) {
let l = pos - steps
let r = pos - 1
diff[l] += 1
diff[r + 1] -= 1
pos = l
}
}
} else {
// state 2: just move, no toggling.
if (dir == 1) {
var steps = x
let maxSteps = n - pos
if (steps > maxSteps) {
steps = maxSteps
}
pos += steps
} else {
var steps = x
let maxSteps = pos - 1
if (steps > maxSteps) {
steps = maxSteps
}
pos -= steps
}
}
} else if (op == 2) {
dir = -dir
} else {
// op == 3
if (state == 1) {
state = 2
} else {
state = 1
}
}
}
// Prefix sum and build output.
let sb = StringBuilder()
var cur = 0
var j = 1
while (j <= n) {
cur += diff[j]
if ((cur % 2) != 0) {
sb.append('1')
} else {
sb.append('0')
}
j += 1
}
println(sb.toString())
return 0
}
要点
- 只关心最终状态:不必逐格维护中间过程,记录每格被翻转的次数,奇偶性即最终状态,把「翻转」抽象成区间加一。
- 边界裁剪步数:实际步数为 (向右)或 (向左),越过边界立即停止,起始格不在翻转范围内。
- 差分优化:一次状态 的移动翻转的是连续区间 ,用差分数组两端各改一处即可,把单次操作从 降到 。
- 状态 只移动不翻转,更新 即可,无需动差分数组。