【安徽集训】字符串

Description

  给定小写字母串 \(s,t\),你可以对 \(s\) 进行以下 \(4\) 种操作:
    1. 在任意位置添加一个字符,代价为 \(a\)
    2. 删除任一字符,代价为 \(b\)
    3. 替换任一字符,代价为 \(c\)
    4. 交换相邻两个字符,代价为 \(d\)
  求将 \(s\) 变为 \(t\) 的最小代价。

  \(|s|,|t|\le 4000,\space 0\lt a,b,c,d\le 10000,\space a+b\le 2d\)

Solution

  有一个不存在操作 4 的子任务……
  只考虑前 3 个操作的话,由于要成为 \(t\) 串一部分的所有字符之间的相对顺序不会变,这就变成了一个普及组 dp 题。
  设 \(f(i,j)\) 表示将 \(s\) 的前 \(i\) 个字符修改为 \(t\) 的前 \(j\) 个字符的代价。
  操作1:\(f(i,j)=f(i,j-1)+a\)
  操作2:\(f(i,j)=f(i-1,j)+b\)
  操作3:\(f(i,j)=f(i-1,j-1)+c\)

  然后考虑丧心病狂的操作 \(4\)
  由于 \(a+b\le 2\times d\),因此每个数只会交换一次。(我之前不知道 constraint 里这条是干嘛的)
  操作4:记 \(k\)\(s\) 中上一个 \(t[j]\) 的位置,\(l\)\(t\) 中上一个 \(s[i]\) 的位置,则 \(f(i,j)=f(k-1,l-1)+d+(i-k-1)*b+(j-l-1)*a\)

#include<bits/stdc++.h>
#define N 4001
using namespace std;
inline int read(){
    int x=0; bool f=1; char c=getchar();
    for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
    for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    if(f) return x;
    return 0-x;
}
int a,b,c,d,f[N][N],s_lst[27],t_lst[27];
char s[N],t[N]; int n,m;
int main(){
    a=read(), b=read(), c=read(), d=read();
    scanf("%s %s",s+1,t+1); n=strlen(s+1), m=strlen(t+1);
    for(int i=1; i<=m; ++i) f[0][i]=f[0][i-1]+a;
    for(int i=1; i<=n; ++i){
        f[i][0]=f[i-1][0]+b;
        memset(t_lst,0,sizeof t_lst);
        for(int j=1; j<=m; ++j){
            f[i][j]=min(f[i][j-1]+a,min(f[i-1][j]+b,f[i-1][j-1]+(s[i]==t[j]?0:c)));
            int k=s_lst[t[j]-'a'], l=t_lst[s[i]-'a'];
            if(k>0 && l>0) f[i][j]=min(f[i][j],f[k-1][l-1]+(i-k-1)*b+d+(j-l-1)*a);
            t_lst[t[j]-'a']=j;
        }
        s_lst[s[i]-'a']=i;
    }
    printf("%d\n",f[n][m]);
    return 0;
}
posted @ 2019-09-26 15:40  大本营  阅读(200)  评论(0编辑  收藏  举报