P9523 [JOISC2022] 复制粘贴 3

P9523 [JOISC2022] 复制粘贴 3 题解

感觉很妙。

题意简述:给三种操作,每次加字符,全选剪切,粘贴,操作有贡献,求到目标串的最小花费。

数据范围:目标串串长\(1\le N \le 2500\)

Solution

考虑怎么构造一个串比较优秀,手玩一下发现,每次构成一个串,我们可以先构成出它的一个连续子串,把它剪切下来,再在建串的时候粘贴。

然后我们发现,这个操作可以是递归的,也可以是并列的

  • 递归的:剪切的串内有是有其他的串剪切而成的。

  • 并列的:枚举断点,前一段用串粘贴,后一段用另一个串粘贴。

发现这很像区间dp的题目,我们是状态\(f(i,j)\)表示构成区间\([i,j]\)的最小贡献。

由上面的粘贴法,我们有状态转移方程:

\[f(i,j)=\min\{f(l,r)+(len(i,j)-len(l,r)\times t)\times A+B+C\times t\} \]

其中\(len(i,j)\)表示字符串长度,即\(j-i+1\)\(t\)表示\([l,r]\)\([i,j]\)中不重复的最多重复了几次。

我们强制钦定\([l,r]\)\([i,j]\)最前且最后位置出现,然后用类似\(f(i,j)=min\{f(i,j-1)+A,f(i+1,j)+A,f(i,j)\}\)的方式转移。

考虑枚举\([l,r]\),显然合法的\([i,j]\)只有$\left \lfloor \frac{n}{len(l,r)} \right \rfloor \(,这个复杂度是调和级数量级的,时间复杂度为\)O(N^2logN)$。

现在只需考虑如何让找到\([l,r]\)变得更快,大神们可以直接SAM

其实枚举两个后缀的\(lcp\)可以\(O(N^2)\)做,然后每次维护前一个串,跳就好了。

代码如下

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=2510;

ll f[N][N],A,B,C;
int n,m,lcp[N][N],pre[N][N];
char s[N];

int main(){
    scanf("%d%s%d%d%d",&n,s+1,&A,&B,&C);
    for(int i=n;i;i--){
        for(int j=i-1;j;j--){
            if(s[i]==s[j])lcp[i][j]=lcp[i+1][j+1]+1;
            else lcp[i][j]=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,j;i+len-1<=n;i++){
            j=i+len-1;
            f[i][j]=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+(k+1)*C+(j-p+1-(k+1)*len)*A);
            }
        }
    printf("%lld\n",f[1][n]);
    return 0;
}
posted @ 2024-10-04 18:34  lichenyu_ac  阅读(21)  评论(0)    收藏  举报