Description
给定一个长度为 \(n (n \le 5000)\) 的字符串,要求将其划分为若干段。每一段要么长度为 \(1\),要么是本段之前部分的子串。前者代价为 \(a\),后者代价为 \(b\),求最小总代价。
Solution
设 \(f[i]\) 表示划分完 \(s[1..i]\) 的最小总代价,则有
\[f[i]=\min(f[i-1]+a, \ \ \min_{j<i, s[j+1..i] \subseteq s[1..j]} f[j]+b)
\]
考虑到 \(f[]\) 具有单调性,因此在第二种转移中,\(j\) 一定要尽可能大,设 \(LCS(i,j)\) 表示 \(s[1..i]\) 与 \(s[1..j]\) 的最长公共后缀,则有
\[f[i]=\min(f[i-1]+a, \min_{j<i} (f_{\max (j, i-LCS(i,j))}))
\]
而 \(LCS(i,j)\) 可以很轻易地用 SA 求出,事实上,由于
\[\begin {aligned}
& LCS(i,j) = 0, & s[i]\neq s[j] \\
& LCS(i,j)=LCS(i-1,j-1)+1, & s[i]=s[j]
\end {aligned}
\]
可以在 \(O(n^2)\) 时间内预处理出 \(LCS(i,j)\),故总时间复杂度为 \(O(n^2)\)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5005;
int f[N],lcs[N][N];
char s[N];
int n,a,b;
signed main()
{
ios::sync_with_stdio(false);
cin>>n>>a>>b>>s+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i]==s[j])
{
lcs[i][j]=lcs[i-1][j-1]+1;
}
}
}
for(int i=1;i<=n;i++)
{
f[i]=f[i-1]+a;
for(int j=1;j<i;j++)
{
f[i]=min(f[i], f[max(j,i-lcs[i][j])]+b);
}
}
cout<<f[n]<<endl;
}