problem id:11030
题意
给定两个字符串 s,t 每次可以进行 4 种操作
- 插入一个字符,花费为 a 。
- 删除一个字符,花费为 b 。
- 替换一个字符,花费为 c 。
- 交换相邻的两个字符,花费为 d 。
※保证 2d≥a+b 。
现在对 s 进行若干次操作,求变成 t 最少需要的代价。
Solution
定义字符串 s,t 的长度分别为 n,m 。
发现 n,m\leq 4000 容易想到可以 \mathcal{O}(nm) 来 dp。
定义 dp_{i,j} 为 s 串的前 i 个字符通过操作得到 t 串的前 j 个字符的最小代价。
故容易发现对于操作1有转移方程:
dp_{i,j} \gets dp_{i,j-1}+a
对于操作2有转移方程:
dp_{i,j} \gets dp_{i-1,j}+b
若 s_i \neq t_j 则对于操作3有转移方程:
dp_{i,j} \gets dp_{i-1,j-1}+c
最后来考虑操作4,由于题目给定 2d≥a+b ,所以容易发现对于同一个字符交换两次一定不如删除后再插入花费少。定义 pre_{s,t_j} 为字符串 s 中 i 之前的第一个 s_k = t_j ,同理定义 pre_{t,s_i} 为字符串 t 中 j 之前的第一个 t_k = s_i 。则对于交换操作,最优的选择应该是交换 s 串上第 i 位和第 pre_{s,t_j} 位,交换 t 串上第 j 位和第 pre_{t,s_i} 位,并将中间部分全部删除并重新插入。对于删除操作,代价为 a \times (i-pre_{s,t_j}-1) ,对于插入操作,代价为 b \times (j-pre_{t,s_i}-1) 。
所以有转移方程:
\large{dp_{i,j} \gets dp_{pre_{s,t_j}-1,pre_{t,s_i}-1}+a \times (i-pre_{s,t_j}-1)+b \times (j-pre_{t,s_i}-1)+d}
对于 pre ,\mathcal{O}(n^2+m^2) 预处理即可,总时间复杂度为 \mathcal{O}(nm+n^2+m^2) 可以通过本题。
code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int res=0,flag=1;
char ch=getchar();
while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
return res*flag;
}
int lass[30][4010],last[30][4010];
int dp[4010][4010];
int main(int argc,const char *argv[])
{
int a=read(),b=read(),c=read(),d=read();
string s,t;
cin>>s>>t;
int n=s.length(),m=t.length();
s=' '+s;
t=' '+t;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=26;j++)
lass[j][i+1]=lass[j][i];
lass[s[i]-'a'+1][i+1]=i;
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=26;j++)
last[j][i+1]=last[j][i];
last[t[i]-'a'+1][i+1]=i;
}
memset(dp,0x3f,sizeof dp);
dp[0][0]=0;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
if(j!=0) dp[i][j]=min(dp[i][j],dp[i][j-1]+a);
if(i!=0) dp[i][j]=min(dp[i][j],dp[i-1][j]+b);
if(i==0||j==0)
continue;
dp[i][j]=min(dp[i][j],dp[i-1][j-1]+c*(s[i]!=t[j]));
int lasa=lass[t[j]-'a'+1][i],lasb=last[s[i]-'a'+1][j];
if(lasa==0||lasb==0)
continue;
dp[i][j]=min(dp[i][j],dp[lasa-1][lasb-1]+(i-lasa-1)*b+(j-lasb-1)*a+d);
}
}
printf("%d",dp[n][m]);
return 0;
}