[lnsyoj2256]消除游戏

题意

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

sol

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

costi={1(xm)mx+1(x<m)

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

  1. r 与后面 k 个值一起删除;
  2. r 与后面 k 个值放在一起(ar1=ar);
  3. 删除区间 [x+1,r1],将 xr 及后面 k 个值放在一起(ax=ar,lx<r1

综上可得转移方程:

fl,r,k=min{fl,r1,0+costk+1,fl,r1,k+1(ar1=ar),minx=lr2{fl,x,k+1+fx+1,r1,0(ax=ar)}}

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

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

代码

#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 @   是一只小蒟蒻呀  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示