题意:给定一个字符串 s ,分成 k 个子串 s1s2...sk,如果当前子串 si 长度为 1 且在前面没有出现过,则花费代价 a ,如果 si 在前面出现过则花费代价 b ,求最小花费。
分析:DP,可以用LCS辅助做, 设 lcs[i][j] 为以 i 和 j 结尾的最长公共字符串(j<i),设dp[i] 是字符串 s 区间[1,i]的花费,则转移方程不难写:
dp[i]=min(dp[i],dp[j]+b) j 代表 [j,i] 在之前出现过,描述不好描述,代码看看就可以明白了。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#define inf 0x3f3f3f3f
using namespace std;
const int N =5010;
char s[N];
int lcs[N][N];
int dp[N];
int n,a,b;
int main()
{
scanf("%d%d%d",&n,&a,&b);
scanf("%s",s+1);
memset(lcs,0,sizeof(lcs));
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(s[i]==s[j])
lcs[i][j]=lcs[i-1][j-1]+1;
else
lcs[i][j]=0;
//处理 lcs 数组
dp[0]=0;
for(int i=1;i<=n;i++)
{
dp[i]=dp[i-1]+a; //初始情况下,花费代价 a
for(int j=1;j<i;j++)
{
if(lcs[i][j]>0) //大于0才有判断的必要
{
int cnt=max(j,i-lcs[i][j]);
//求最大起点,避免 len=lcs[i][j],而[j-len,j]和[i-len,i]区间虽然相等,
//但是 j>i-len(表示两区间相交) 而不满足题意。
dp[i]=min(dp[i],dp[cnt]+b);
}
}
}
printf("%d",dp[n]);
}