祖玛


数据规模:1≤n≤5001 \le n \le 500,1≤ci≤n1 \le c_i \le n。

思路

一次操作删掉的是什么。 操作删掉的是「当前序列」里的一段回文,换成原序列的下标来看:它删掉一组下标 i1<i2<⋯<imi_1<i_2<\cdots<i_m,这组下标的颜色按顺序读是回文(ci1=cimc_{i_1}=c_{i_m},ci2=cim−1c_{i_2}=c_{i_{m-1}},依此类推),并且每对相邻下标 ij,ij+1i_j,i_{j+1} 之间的宝石在此刻都已经删干净了。

由此可以看出整个解的分层结构:任取两次操作,它们删掉的下标集合不会交叉——若 AA 先于 BB 执行,BB 执行时 BB 的下标在序列里连续,AA 的下标不可能夹在 BB 的最小、最大下标之间。所以每个操作都「套」在另一个操作的某个空档里,或者说落在互不相交的区间中,于是可以按区间递归地描述,适合区间 DP。

状态。 dp[l][r]dp[l][r] 表示把原序列区间 [l,r][l,r] 内的宝石全部删光所需的最少操作次数,dp[l][l]=1dp[l][l]=1。

转移一:拆成两段独立删。 左右两段各自删光,彼此不干扰:

dp[l][r]≤min⁡l≤k<r(dp[l][k]+dp[k+1][r]).dp[l][r]\le\min_{l\le k<r}\bigl(dp[l][k]+dp[k+1][r]\bigr).

转移二:两端同色时可以并进同一次操作。 设 cl=crc_l=c_r,取 [l+1,r−1][l+1,r-1] 的任一最优方案,考察它的最后一次操作,设它删掉的回文是 p1<⋯<pmp_1<\cdots<p_m。这次操作执行时,[l+1,r−1][l+1,r-1] 里除这 mm 颗宝石外都已删空,所以 ll 紧挨着 p1p_1、rr 紧挨着 pmp_m;又 cl=crc_l=c_r 且 p1,…,pmp_1,\dots,p_m 是回文,故 l,p1,…,pm,rl,p_1,\dots,p_m,r 仍然是回文,把这最后一次操作换成删除它们即可,操作数不变。于是

dp[l][r]≤dp[l+1][r−1](cl=cr).dp[l][r]\le dp[l+1][r-1]\qquad(c_l=c_r).

注意这里代价是 +0+0 而不是 +1+1:中间那段的最后一次操作被吸收进了含两端的那一次操作。r=l+1r=l+1(中间为空)要单独取 dp[l][l+1]=1dp[l][l+1]=1。

容易写错的一版是 dp[l+1][r−1]+1dp[l+1][r-1]+1,即「先把中间删空,再单独删两端」,它漏掉了「整个区间本身就是回文」的情形:2,3,22,3,2 一次就能删完,而 +1+1 的写法得到的是 22。

转移的完备性。 任取 [l,r][l,r] 的一个最优方案,设删掉 ll 的那次操作删掉的下标是 l=i1<i2<⋯<iml=i_1<i_2<\cdots<i_m,它内部的空档都更早被删空,imi_m 右侧的部分记作 TT。

  • m=1m=1:该操作只涉及 ll,费用为 1+dp[l+1][r]1+dp[l+1][r],转移一取 k=lk=l 即可。
  • m≥2m\ge2:先用转移一在 imi_m 处拆开,dp[l][r]≤dp[l][im]+dp[im+1][r]dp[l][r]\le dp[l][i_m]+dp[i_m+1][r],后一项正是 TT 的费用。对 [l,im][l,i_m] 反复使用转移二剥掉最外层的一对:由 ci1=cimc_{i_1}=c_{i_m} 有 dp[i1][im]≤dp[i1+1][im−1]dp[i_1][i_m]\le dp[i_1+1][i_m-1],而区间 [i1+1,im−1][i_1+1,i_m-1] 里的 G0=(i1,i2)G_0=(i_1,i_2)、Gm−1=(im−1,im)G_{m-1}=(i_{m-1},i_m) 两段空档可以由转移一单独结算,剩下的 i2,…,im−1i_2,\dots,i_{m-1} 是同一个结构、长度减二的问题,归纳下去即可。剥到最内层时,奇数长度的核心是单颗宝石(恰好对应外层那次操作的 11 次),偶数长度的核心是一对同色宝石(转移二的 +0+0 把它们并进同一次操作)。因此总费用不超过原方案的 1+∑dp(Gj)1+\sum dp(G_j),两个转移足以覆盖所有情形。

也就是说,[l,r][l,r] 的最优解中,删掉 ll 的那次操作要么只含 ll,要么从 ll 一路延伸到某个 imi_m,而 imi_m 之后与之前都可以拆成更小的区间问题,这正是上面两个转移所枚举的。

检验。 样例 1,2,3,2,1,41,2,3,2,1,4 中前五颗构成回文,先删它们再删最后一颗,代价 22,与样例一致;而整个 [l,r][l,r] 是回文时(如 2,3,22,3,2)答案为 11,正是转移二 +0+0 的效果。

复杂度

状态数 O(n2)O(n^2),每个状态枚举 O(n)O(n) 个切点,时间 O(n3)O(n^3)(n=500n=500 时约 5003/6≈2×107500^3/6\approx2\times10^7 次运算),空间 O(n2)O(n^2)。

仓颉实现

二维表按一维数组存储,下标为 l⋅(n+1)+rl\cdot(n+1)+r。按区间长度从小到大递推:先处理两端同色的合并,再枚举切点。

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

main() {
    let reader = getStdIn()
    let n = Int64.parse(reader.readln().getOrThrow())
    let c = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let INF: Int64 = 1000000000
    let w = n + 1
    let dp = Array<Int64>(w * w, { _ => 0 })
    for (i in 0..n) {
        dp[i * w + i] = 1
    }
    for (len in 2..=n) {
        for (l in 0..=(n - len)) {
            let r = l + len - 1
            var best: Int64 = INF
            if (c[l] == c[r]) {
                if (len == 2) {
                    best = 1
                } else {
                    best = dp[(l + 1) * w + (r - 1)]
                }
            }
            let base = l * w
            for (k in l..r) {
                let v = dp[base + k] + dp[(k + 1) * w + r]
                if (v < best) {
                    best = v
                }
            }
            dp[base + r] = best
        }
    }
    println(dp[n - 1])
}