[R45D]货架和沙威玛
题目
按顺序排列的 个调料品,第 个风味值为 。可以进行若干次操作:选择一个区间 (),使得该区间构成一个长度至少为 的等差数列,然后将该区间从序列中删除(两侧拼接)。求最后货架上最少剩下几个调料品。多组测试数据,。
思路
每次操作删掉至少 个元素。注意到 任意两个相邻元素都构成一个长度为 的等差数列,因此总是可以把任意相邻的一对删掉。反复删相邻对即可把序列长度归约到 :偶数长度一定可以全部删空,奇数长度至多剩 个。所以答案只可能是 或 :
- 为偶数:答案恒为 。
- 为奇数:答案为 当且仅当能把整个序列删空,否则为 。
奇数 要全部删空,必须出现一次删掉 奇数个 元素的操作(删掉长度为奇数的等差数列),把长度从奇数变成偶数。最小的这种结构是删掉 个成等差数列的元素。关键结论是:
奇数 能被全部删空,当且仅当存在下标 ,满足 (即 成等差数列, 是 的等差中项),并且 同奇偶、 与 异奇偶、。
奇偶条件的来源:被最后一次操作删掉的三个元素,是原序列的一个子序列,它们之间的元素已经更早被成对删掉。成对删掉意味着 与 之间、 与 之间各自删掉了偶数个元素,故 同奇偶、 同奇偶;但三个下标 本身要保持 且整体跨越奇偶,再结合「 必须在 之后」可归纳出 与 异奇偶。反过来的构造(先成对地清空 之间与 之间的元素,再把成等差数列的三个相邻元素一次删掉,最后用相邻对清空剩余偶数个元素)说明该条件也是充分的。
实现上,用两个哈希表分别记录「下标为奇数」「下标为偶数」的位置上每个值 最后出现的位置(取最大下标即可,因为只需要存在一个 的)。然后枚举所有同奇偶对 (),令目标值 ,在与 异奇偶的那个哈希表里查 是否存在且位置 。一旦查到就直接输出 ;全部枚举完仍未查到则输出 。
复杂度
- 时间复杂度:(同奇偶对约 个,每次哈希表查询均摊 ),多组数据合计 。
- 空间复杂度:。
仓颉实现
查看仓颉实现
import std.convert.*
import std.console.*
import std.collection.*
func solve(reader: ConsoleReader): Unit {
let n = Int64.parse(reader.readln().getOrThrow())
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
// 两个 map:mp0 记录偶数位(下标 %2==0),mp1 记录奇数位(下标 %2==1)每个值最后出现位置(1-based)
var mp0 = HashMap<Int64, Int64>()
var mp1 = HashMap<Int64, Int64>()
var i: Int64 = 0
while (i < n) {
let idx = i + 1
let v = a[i]
if (idx % 2 == 1) {
mp1[v] = idx
} else {
mp0[v] = idx
}
i += 1
}
if (n % 2 == 0) {
println(0)
return
}
var ii: Int64 = 0
while (ii < n) {
let i1 = ii + 1
var j1 = i1 + 1
while (j1 <= n) {
let jPar = j1 % 2
let target = 2 * a[j1 - 1] - a[i1 - 1]
var kpos: Int64 = -1
if (jPar == 1) {
if (mp0.contains(target)) {
kpos = mp0[target]
}
} else {
if (mp1.contains(target)) {
kpos = mp1[target]
}
}
if (kpos > j1) {
println(0)
return
}
j1 += 2
}
ii += 1
}
println(1)
}
main() {
let reader = Console.stdIn
let t = Int64.parse(reader.readln().getOrThrow())
var c: Int64 = 0
while (c < t) {
solve(reader)
c += 1
}
}