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;
}

浙公网安备 33010602011771号