有一个操作是将 \(X\) 置为空串,这意味着我们不一定从左往右添加字符,例如
abcbcbcbcbc
。
但是这个题目一旦套上区间 dp 是一定会炸的,但是我们发现如果我们要在中间开始用剪贴板的话,前面的字符只能一个一个填,所以我们一定是从一整个后缀转移过来的。
很显然我们枚举的后缀 \(i\) 不会再被后缀 \(j\) 转移,否则我们不如直接枚举 \(j\) 这个后缀,也就是我们枚举完后缀后一定是从左往右填字符。
那么我们对每一个后缀单独计算,\(f_i\) 表示已经填完了 \(i\) 这个前缀的字符的最小代价,每一次我们可以填上一个字符,或者将他复制几倍,时间复杂度线性对数,总时间复杂度 \(O(n^2\ln n)\)。
不过这个做法假掉了,有可能我们依然保留剪贴板中的字符串,然后在中间加字符。
但是如果我们再保留一个剪贴板中的字符前缀就炸了。
题解完全是另一个思维:
直接考虑区间 dp,但是并没有记录和剪贴板相关的信息,而是非常粗暴的,设 \(f_{l,r}\) 表示拼出 \([l,r]\) 的最小代价。
考虑最后一步,如果是添加字符,\(f_{l,r}\rightarrow f_{l,r+1}\);如果是复制,那么一定是先拼出某一个区间,然后将整个区间覆盖若干次,他们之间的间隙直接加字符串。
那么我们枚举覆盖了 \(k\) 次,这是线性对数的时间。然后我们找到最右边的 \(p\),满足 \([p,r]\) 能找到 \(k\) 个互不相交的 \(S[l,...,r]\),那么小于等于 \(p\) 的都能转移到。
考虑到 \(p\) 到 \([p,p+len-1]\) 同样保存了 \([i,j]\) 的所有字符串,那么我们每一次找到和 \(p\),\(lcp\) 大于等于 \(len\) 的即可,这个可以预处理。
好缜密的做法,每一步都很细节。
#include<bits/stdc++.h>
using namespace std;
#define N 2505
#define ull unsigned long long
int n,lcp[N][N],pr[N][N];
char a[N];
ull A,B,C,f[N][N];
int main(){
scanf("%d%s%llu%llu%llu",&n,a+1,&A,&B,&C);
for(int i=n;i;i--)for(int j=n;j;j--)lcp[i][j]=(a[i]==a[j])?lcp[i+1][j+1]+1:0;
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++)pr[i][min(lcp[i][j],i-j)]=max(pr[i][lcp[i][j]],j);
for(int j=n;j;j--)pr[i][j]=max(pr[i][j],pr[i][j+1]);
}
memset(f,0x3f,sizeof(f));
for(int r=1;r<=n;r++){
for(int l=r;l;l--){
if(l!=r)f[l][r]=min({f[l][r],f[l][r-1]+A,f[l+1][r]+A});
else f[l][l]=A;
for(int k=1,p=l;p;k++,p=pr[p][r-l+1])
f[p][r]=min(f[p][r],f[l][r]+A*(r-p+1-k*(r-l+1))+B+C*k);
}
}
printf("%llu",f[1][n]);
}