跳跃选择


数据规模:1≤N≤1001 \le N \le 100,1≤X≤1041 \le X \le 10^4,1≤Ai<Bi≤1001 \le A_i < B_i \le 100。

思路

本质是子集和判定的线性 DP。 设 dp[s]\textit{dp}[s] 表示处理完前若干次跳跃后,能否恰好到达距离 ss。初始 dp[0]=true\textit{dp}[0] = \text{true},其余为 false\text{false}。

每读入一次跳跃的两种距离 AiA_i、BiB_i,就做一次转移:对所有可达的 ss,把 s+Ais + A_i 与 s+Bis + B_i 标记为可达。用滚动数组避免覆盖,即先复制旧数组,再在新数组上标记。

超过 XX 的状态可以直接丢弃。 由于 Ai,Bi≥1A_i, B_i \ge 1,跳跃总距离单调不减,一旦超过 XX 便再也不可能回到 XX。因此只需维护 0≤s≤X0 \le s \le X 的范围,数组开 X+1X + 1 即可。

处理完全部 NN 次跳跃后,dp[X]\textit{dp}[X] 即为答案:可达输出 Yes,否则输出 No。

复杂度

时间 O(NX)≤106O(NX) \le 10^6,额外空间 O(X)O(X)。

仓颉实现

查看仓颉实现
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 x = first[1]
    var dp = Array<Bool>(x + 1, { _ => false })
    dp[0] = true
    var i: Int64 = 0
    while (i < n) {
        let ab = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
        let a = ab[0]
        let b = ab[1]
        let ndp = Array<Bool>(x + 1, { _ => false })
        var s: Int64 = 0
        while (s <= x) {
            if (dp[s]) {
                if (s + a <= x) {
                    ndp[s + a] = true
                }
                if (s + b <= x) {
                    ndp[s + b] = true
                }
            }
            s += 1
        }
        dp = ndp
        i += 1
    }
    if (dp[x]) {
        println("Yes")
    } else {
        println("No")
    }
}