把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

P9523 [JOIST 2022] 复制粘贴 3 / Copy and Paste 3 分析

题目概述

在这个编辑器中,可以执行如下几种操作来输入某个字符串,设 \(X\) 为屏幕上的字符串,\(Y\) 为剪切板中的字符串,初始均为空串:

  • 操作 A:输入字符 \(c\),即将 \(X\) 更新为 \(X+c\)
  • 操作 B:选择所有字符并剪切,即将 \(Y\) 更新为 \(X\),并将 \(X\) 置为空串。
  • 操作 C:将剪切板中的字符串粘贴到当前字符串末尾,即将 \(X\) 更新为 \(X+Y\)

对于字符串或字符 \(x,y\)\(x+y\) 表示将 \(x\)\(y\) 顺次拼接得到的结果。使用一次操作 A,B,C 分别要花费 \(A,B,C\) 单位时间。

你安装了“没啥用编辑器”,并想要尽可能快地输入一个长度为 \(N\) 的字符串 \(S\)

你需要计算出最少需要花费多少时间。

对于所有数据,满足:\(1\leq N\leq 2500\)

分析

好题!

首先考虑到有先后顺序问题,直接考虑区间 \(dp\)

\(f_{i,j}\) 表示得到 \([i,j]\) 的最小代价。

显然的,有加字符的代价进行转移。

当然还需要考虑怎么转移粘贴的情况。

考虑 \([i,j]\rightarrow[p,j]\) 进行转移(\(p<i\),肯定考虑最大的 \(p\),更优)。

考虑枚举一下 \([p,j]\) 出现 \([i,j]\) 的次数。

然后进行转移即可,转移很简单。

这里的时间复杂度是 \(\mathcal{O}(n^2\log n)\) 的,非常优秀(假设 \(\mathcal{O}(1)\) 得到 \(p\))。

考虑怎么快速找到 \(p\),这也是本题一大难点。

大概来说是这样的,这个过程十分巧妙。

我们考虑从左端点为 \(i\) 的怎么扩展到左端点为 \(j\) 的。

首先我们需要知道长度,其次我们需要这个长度下的最大的上一个。

后面哪个不妨设 \(pre_{i,j}\) 表示以 \(i\) 为左端点与他前 \(j\) 个子串相等的最大的上一个。

依据这个设 \(lcp_{i,j}\) 表示以 \(i\) 为左端点和以 \(j\) 为左端的他们的最长前缀(不重叠)。

这个是好维护的,可以 \(\mathcal{O}(n^2)\) 解决,而且我们不难发现他其实是不一定连续的,满足我们的需求。

代码

时间复杂度 \(\mathcal{O}(n^2\log n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 2505
using namespace std;
int lcp[N][N],pre[N][N],f[N][N];
int A,B,C,n;
char s[N];
signed main(){
    cin >> n;
    scanf("%s",s + 1);
    cin >> A >> B >> C;
    for (int i = n;i;i --) {
        for (int j = i - 1;j;j --) {
            lcp[i][j] = (s[i] == s[j] ? lcp[i + 1][j + 1] + 1 : 0);
            lcp[i][j] = min(lcp[i][j],i - j);
            if (!pre[i][lcp[i][j]]) pre[i][lcp[i][j]] = j;
        }
        for (int j = n;j;j --) pre[i][j] = max(pre[i][j],pre[i][j + 1]);
    }
    memset(f,0x3f,sizeof f);
    for (int i = 1;i <= n;i ++) f[i][i] = A;
    for (int len = 1;len <= n;len ++)
        for (int i = 1;i + len - 1 <= n;i ++) {
            int j = i + len - 1;
            f[i][j] = min(min(f[i][j],f[i + 1][j] + A),f[i][j - 1] + A);
            int p = i;
            for (int k = 1;k <= j / len;k ++) {
                p = pre[p][len];
                if (!p) break;
                f[p][j] = min(f[p][j],f[i][j] + B + C * (k + 1) + (j - p + 1 - (k + 1) * len) * A);
            }
        }
    cout << f[1][n];
    return 0;
}
posted @ 2025-11-18 21:56  high_skyy  阅读(5)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end