[R81D] 货物
数据规模:,。
思路
取法的形状。 每次只能在当前剩余货物里取最左或最右的一件,取 次后,从左边共取走 件、从右边共取走 件。左边取走的必然是原序列的前缀 ,右边取走的必然是后缀 ,中间是否交替取并不影响最终的编号集合。反过来,任意 都可以先取 次左边、再取 次右边实现,因此 所有可能的取法恰好是这 种「前缀 与后缀 的并」。
合法的判定。 一共取走 件货物,而颜色恰好只有 种,所以「每种颜色恰好出现一次」等价于 取走的 件货物颜色两两不同,也就是这 件里没有重复颜色。
的情形。 此时同一 的前缀与后缀互不重叠。不同的 也给出不同的下标集合:设 ,位置 属于 的后缀( 的后缀起点更靠左),却被 的后缀起点挡在外面;它也不属于 的前缀,因为 时 。于是答案就是满足条件的 的 个数,不需要去重。
下标(从 开始编号)随 从 递增到 只移动两个位置:
- 由 变到 时,前缀多出位置 ;
- 同时后缀少掉位置 。
因此用一个长度为 的颜色计数数组 和「当前出现次数不少于 的颜色个数」 就能维护重复情况:加入时若计数从 变成 则 加 ,移出时若计数从 变成 则 减 。初始窗口取 对应的后缀 。每个 在转移前检查, 就计入答案。
注意 只在计数跨过 时变化,写成「计数 就加一」会把出现三次及以上的颜色重复计数,导致后续窗口的重复数被高估。
的情形。 前缀与后缀拼起来始终是整个序列, 取任何值得到的都是全集 ,按题意它们是同一种取法。因此答案只能是 (全部颜色互不相同)或 。
复杂度
扫描一遍序列并滑动 个窗口,时间 ;颜色计数数组空间 ,存储输入序列空间 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
main() {
let reader = getStdIn()
let first = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = first[0]
let m = first[1]
let c = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let cnt = Array<Int64>(m + 1, { _ => 0 })
var dup: Int64 = 0
var ans: Int64 = 0
if (m == n) {
for (i in 0..n) {
let x = c[i]
cnt[x] += 1
if (cnt[x] == 2) {
dup += 1
}
}
if (dup == 0) {
ans = 1
}
println(ans)
} else {
for (i in (n - m)..n) {
let x = c[i]
cnt[x] += 1
if (cnt[x] == 2) {
dup += 1
}
}
for (l in 0..=m) {
if (dup == 0) {
ans += 1
}
if (l < m) {
let a = c[l]
cnt[a] += 1
if (cnt[a] == 2) {
dup += 1
}
let b = c[n - m + l]
cnt[b] -= 1
if (cnt[b] == 1) {
dup -= 1
}
}
}
println(ans)
}
}