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


相关文章:

  • 2022-12-23
  • 2022-02-17
  • 2021-07-18
  • 2021-10-16
  • 2021-07-18
  • 2021-06-16
  • 2022-12-23
  • 2021-12-11
猜你喜欢
  • 2022-12-23
  • 2021-11-01
  • 2021-06-06
  • 2021-06-27
  • 2022-12-23
  • 2022-12-23
  • 2021-12-17
相关资源
相似解决方案