[R78E] 递增


数据规模:1≤n≤5001 \le n \le 500,−109≤ai≤109-10^9 \le a_i \le 10^9。

思路

一个直观的贪心是:从左往右,每段取「极差刚好大于上一段极差的最短前缀」。它是错的。反例 a=[6,6,2,0,4,5,1,1,6]a=[6,6,2,0,4,5,1,1,6]:贪心得到 [6],[6,2],[0,4,5,1,1,6][6],[6,2],[0,4,5,1,1,6] 共 33 段(极差 0,4,60,4,6),但 [6,6],[2,0],[4,5,1],[1,6][6,6],[2,0],[4,5,1],[1,6] 的极差为 0,2,4,50,2,4,5,共 44 段。最短切法会把后面本可组成小极差段的元素「错相」,导致后续段被迫吞下过大极差,段数反而变少,需要 DP。

设段 [l,r][l,r] 的极差为 w(l,r)=max⁡al..r−min⁡al..rw(l,r)=\max a_{l..r}-\min a_{l..r}。对每个位置 ii,考虑前缀 a1..ia_{1..i} 的所有合法划分,每个划分只保留两个信息:段数 cc 与最后一段的极差 rr。状态 (c1,r1)(c_1,r_1) 被 (c2,r2)(c_2,r_2) 支配(当 c1≤c2c_1\le c_2 且 r1≥r2r_1\ge r_2):任何能接在 (c1,r1)(c_1,r_1) 后面的段(极差要大于 r1r_1,自然也大于 r2r_2)都能接在 (c2,r2)(c_2,r_2) 后面,且后者段数不少,所以被支配的状态可以丢弃。于是每个 ii 只需维护一条 Pareto 前沿:按 rr 递增排列、cc 也严格递增的状态序列。

转移:枚举最后一段的起点 jj(1≤j≤i1\le j\le i),最后一段为 aj..ia_{j..i},极差 R=w(j,i)R=w(j,i)。在前缀 j−1j-1 的前沿中查询所有 r<Rr<R 的状态里最大的 cc,产生候选状态 (R, c+1)(R,\,c+1)。j=1j=1 时视作接在虚拟状态 (c=0,r=−1)(c=0,r=-1) 之后,即整个前缀单独成段。

实现上从 j=ij=i 开始向左扫,顺带维护 aj..ia_{j..i} 的最小值与最大值即可 O(1)O(1) 得到每个 RR;且 jj 减小时 RR 单调不减,候选状态天然按 RR 升序产生。前沿中 rr 升序、cc 严格升序,查询「r<Rr<R 的最大 cc」用二分查找;合并候选时顺序扫描,只保留 cc 严格大于此前所有候选的状态,即得位置 ii 的前沿。

答案即位置 nn 的前沿中最大的 cc(序列末尾元素)。

复杂度

前沿长度不超过最大段数 kk:严格递增的非负整数极差至少为 0,1,…,k−10,1,\dots,k-1,极差为 00 的段至少 11 个元素,其余段至少 22 个,故 k≤⌊(n+1)/2⌋k\le\lfloor(n+1)/2\rfloor。

每个位置枚举 jj 共 O(n)O(n) 次转移,每次二分查询 O(log⁡n)O(\log n),合并候选 O(n)O(n),总时间 O(n2log⁡n)O(n^2\log n);保存所有位置的前沿,空间 O(nk)=O(n2)O(nk)=O(n^2)。n≤500n\le 500 下非常宽裕。

仓颉实现

查看仓颉实现
import std.env.*
import std.convert.*
import std.collection.*

main() {
    let reader = getStdIn()
    let n = Int64.parse(reader.readln().getOrThrow())
    let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let nn = n
    let frR = Array<ArrayList<Int64>>(nn, { _ => ArrayList<Int64>() })
    let frC = Array<ArrayList<Int64>>(nn, { _ => ArrayList<Int64>() })
    var i: Int64 = 0
    while (i < n) {
        let candR = ArrayList<Int64>()
        let candC = ArrayList<Int64>()
        var mn = a[i]
        var mx = a[i]
        var j = i
        while (j >= 0) {
            if (a[j] < mn) {
                mn = a[j]
            }
            if (a[j] > mx) {
                mx = a[j]
            }
            let r = mx - mn
            var bestc: Int64 = -1
            if (j == 0) {
                bestc = 0
            } else {
                let rs = frR[j - 1]
                let cs = frC[j - 1]
                var lo: Int64 = 0
                var hi = rs.size - 1
                var idx: Int64 = -1
                while (lo <= hi) {
                    let mid = (lo + hi) / 2
                    if (rs[mid] < r) {
                        idx = mid
                        lo = mid + 1
                    } else {
                        hi = mid - 1
                    }
                }
                if (idx >= 0) {
                    bestc = cs[idx]
                }
            }
            if (bestc >= 0) {
                candR.add(r)
                candC.add(bestc + 1)
            }
            j -= 1
        }
        let R2 = ArrayList<Int64>()
        let C2 = ArrayList<Int64>()
        var maxc: Int64 = 0
        var k: Int64 = 0
        while (k < candR.size) {
            let c = candC[k]
            if (c > maxc) {
                R2.add(candR[k])
                C2.add(c)
                maxc = c
            }
            k += 1
        }
        frR[i] = R2
        frC[i] = C2
        i += 1
    }
    let last = frC[n - 1]
    println(last[last.size - 1])
}