[lnsyoj2256]消除游戏

题意

给定序列 \(a\),每次可以选择其中长度 \(>m\) 且完全相同的一段并删除,并将序列的剩余部分拼合成一段;或在任意位置插入一个数。求最终将序列 \(a\) 清空的最小操作数

sol

很显然,本题的插入操作是为了能够使一段数能够凑够 \(m\) 个,因此我们可以只考虑删除操作,只在操作上下手脚即可。具体地,我们定义 \(cost_i\) 表示将长度为 \(i\) 的一段删除所需要的最小操作数,显然可得:

\[cost_i=\left\{\begin{matrix} 1(x\ge m) \\ m-x+1(x<m) \end{matrix}\right.\]

本题与[lnsyoj2239/luoguP5336/THUSC2016]成绩单较为相似,都为可以拼合的区间问题。设计 \(f_{l,r,k}\) 表示将区间 \([l,r]\) 清空,且 \(r\) 之后有 \(k\) 个元素与 \(a_r\) 相同的最小代价。 我们可以分为三种情况(实际上是两种,分为三种是为了减少细节处理)

  1. \(r\) 与后面 \(k\) 个值一起删除;
  2. \(r\) 与后面 \(k\) 个值放在一起(\(a_{r-1}=a_r\));
  3. 删除区间 \([x+1,r-1]\),将 \(x\)\(r\) 及后面 \(k\) 个值放在一起(\(a_x=a_r,l\le x < r-1\)

综上可得转移方程:

\[f_{l,r,k}=\min\{f_{l,r-1,0} + cost_{k+1},f_{l,r-1,k+1}(a_{r-1}=a_r),\min_{x=l}^{r-2}\{f_{l,x,k+1} + f_{x+1,r-1,0}(a_x=a_r)\}\} \]

由于数据基本随机,因此实际的可用状态并不多,因此可以使用记忆化搜索。

这里有一个 trick,我们使 \(f_{l,r,m}\) 表示将区间 \([l,r]\) 清空,且 \(r\) 之后有 \(\ge k\) 个元素与 \(a_r\) 相同的最小代价,这是因为 \(k\) 这一维的主要目的是为了计算代价,且可以证明,每次将一段相同的区间全部拿完代价最小。这样,我们就将每次初始化所需的时空复杂度缩小到了 \(O(n^2m)\)

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 405;

int n, m;
int a[N];
int f[N][N][15];
int T;

int cost(int x){
    if (x >= m) return 1;
    else return m - x + 1;
}

int dfs(int l, int r, int k){
    if (k >= m) k = m;
    if (f[l][r][k]) return f[l][r][k];
    if (l == r) return cost(k + 1);
    int res = dfs(l, r - 1, 0) + cost(k + 1);
    if (a[r - 1] == a[r]) res = min(res, dfs(l, r - 1, k + 1));
    for (int i = l; i < r - 1; i ++ )
        if (a[i] == a[r]) res = min(res, dfs(l, i, k + 1) + dfs(i + 1, r - 1, 0));
    return f[l][r][k] = res;
}

int main(){
    scanf("%d", &T);
    for (int u = 1; u <= T; u ++ ) {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
        memset(f, 0, sizeof f);
        printf("%d\n", dfs(1, n, 0));
    }

    return 0;
}
posted @ 2024-08-13 20:54  是一只小蒟蒻呀  阅读(20)  评论(0编辑  收藏  举报