字符串袋子
- 难度 普及+
- 时限 1s
- 空限 256m
- 线性 DP
数据规模:,,,,所有字符串只含小写英文字母。
思路
状态。 由于 始终是 的一个前缀(任何时刻若 不是 的前缀,后面再接字符串也永远拼不回 ),所以只需记录 已经匹配了多少位。设 表示处理完当前这批袋子后, 恰为 的前 位时的最少费用。初值 ,其余为正无穷(不可达)。
转移。 逐个袋子更新。对第 个袋子,有两种选择:
- 跳过: 原样保留;
- 选其中某个串 :只有当 可达、 且 的第 位起与 完全相同(即 )时,才能从 走到 ,费用加 。
为什么必须用两个数组。 一个袋子最多只能选一个串,因此同一轮内的所有转移都只能从处理该袋之前的 出发,不能读到本轮刚更新的值。实现上先令 new 复制一份 (对应「跳过」),再用旧的 去更新 new;一轮结束把 new 赋回 。若只用一个数组就地进行,就等于允许同一个袋子里连选多个串,会把答案算小。
边界。 长度超出 的转移直接忽略;比较前先判 ,避免越界切片。全部袋子处理完后, 仍为无穷即说明无法拼出 ,输出 。
复杂度
时间复杂度 ,其中 为串长(字符串比较的开销),代入数据上限约为 ;空间复杂度 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
main() {
let reader = getStdIn()
let t = reader.readln().getOrThrow()
let n = Int64.parse(reader.readln().getOrThrow())
let m = t.size
let INF: Int64 = 1000000000000
var dp = Array<Int64>(m + 1, { _ => INF })
dp[0] = 0
for (_ in 0..n) {
let parts = reader.readln().getOrThrow().split(" ", removeEmpty: true)
let a = Int64.parse(parts[0])
let nd = Array<Int64>(m + 1, { _ => INF })
for (j in 0..=m) {
nd[j] = dp[j]
}
for (k in 1..=a) {
let s = parts[k]
let ls = s.size
for (len in 0..=m) {
if (dp[len] < INF && len + ls <= m) {
if (t[len..(len + ls)] == s) {
let ni = len + ls
if (dp[len] + 1 < nd[ni]) {
nd[ni] = dp[len] + 1
}
}
}
}
}
dp = nd
}
if (dp[m] >= INF) {
println(-1)
} else {
println(dp[m])
}
}