线性DP——伴随插入、删除操作

T1编辑距离

题目描述

\(A\)\(B\) 是两个字符串。我们要用最少的字符操作次数,将字符串 \(A\) 转换为字符串 \(B\)。这里所说的字符操作共有三种:

  1. 删除一个字符;
  2. 插入一个字符;
  3. 将一个字符改为另一个字符。

\(A, B\) 均只包含小写字母。

输入格式

第一行为字符串 \(A\);第二行为字符串 \(B\);字符串 \(A, B\) 的长度均小于 \(2000\)

输出格式

只有一个正整数,为最少字符操作次数。

样例 #1

样例输入 #1

sfdqxbw
gfdgw

样例输出 #1

4

提示

对于 \(100 \%\) 的数据,\(1 \le |A|, |B| \le 2000\)

最近遇到了很多类似这样的题,所以把这些题整理在一起。刚开始我以为这个黄题,可以使用贪心模拟来做,但是贪心是错的,我们来DP考虑

对于修改操作而言,非常好做,因为它不会改变字符串的长度

对于插入和删除操作,插入会增加长度,删除操作会减少长度,对于这样的DP,我们会设定\(dp[i][j]表示将A串前i个位置变成B串前j个位置的最少编辑距离是多少?\)刚开始的想法是,如果我们在A串第i个位置插入字符串之后,A串的i+1个位置就变化了,这怎么DP呢?

做这种题的宗旨是无论插入、删除操作,我们不要让插入和删除操作影响原来的位置关系

对于这道题而言,假设\(a[i]==b[j],那么f[i][j]=f[i-1][j-1],如果a[i]!=b[j]呢,我们需要进行插入、删除和修改操作,如果我们进行插入操作,那么b[j]就要和插入的字符匹配,a[i]和b[j-1]进行匹配,f[i][j]=f[i][j-1]+1,如果我们进行的是删除操作呢,b[j]和a[i-1]就匹配好了,所以f[i][j]=f[i-1][j]+1,如果是修改操作的话,就更简单了,f[i][j]=f[i-1][j-1]+1,这个题就做完了\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
char s[2005],t[2005];
int f[2005][2005]; 
int main(){
	scanf("%s%s",s+1,t+1);
	int n=strlen(s+1);
	int m=strlen(t+1);
	memset(f,0x3f,sizeof f);
	f[0][0]=0;//边界,假设只有一个字符 
	for(int i=1;i<=n;i++)
		f[i][0]=i;// 需要进行i次删除操作
	for(int i=1;i<=m;i++)
		f[0][i]=i;// 需要进行i次添加操作 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			if(s[i]==t[j]) f[i][j]=f[i-1][j-1];
			else{
				f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
				f[i][j]=min(f[i][j],f[i-1][j-1]+1);
			}
		}
	cout<<f[n][m]<<endl;
	return 0;
}

T2 [CCPC 2023 北京市赛] 广播

题目描述

小 I 正在学习使用 Pytorch。这是一个非常热门的用于机器学习训练的 Python 库。

小 I 注意到,Pytorch 中对于张量运算有称作“广播”(broadcast)的机制。你可以认为张量是高维数组。对于一个 \(k\) 维张量 \(A\),我们用长度为 \(k\) 的序列 \((a_1,a_2,\cdots,a_k)\) 表示其各个维度的长度,也就是说 \(A\) 是一个 \(a_1 \times a_2 \times \cdots \times a_k\) 的张量。

对于两个张量 \(A\)\(B\),设它们的维度分别为 \((a_1,a_2,\cdots,a_m)\)\((b_1,b_2,\cdots,b_n)\),称 \(A\)\(B\)可广播的,当且仅当以下性质成立:

  • 对于任意整数 \(0 \le i \le \min(n,m) - 1\),要么 \(a_{m-i} = b_{n-i}\),要么 \(a_{m-i}\)\(b_{n-i}\) 中至少有一个是 \(1\)

现在小 I 有两个张量,它们的维度分别是 \((p_1,p_2,\cdots,p_m)\)\((q_1,q_2,\cdots,q_n)\),它们不一定是可广播的。

为此,小 I 可以使用 Pytorch 内置的函数进行若干次操作(可以不做操作),每次操作对序列 \(p\)\(q\) 进行以下修改:

  • 选择 \(p\)\(q\),在选定序列的任意一个位置插入一个 \(1\)

小 I 想知道他最少要多少次操作才能让两个张量变为可广播的。

输入格式

输入的第一行两个整数 \(m,n(1 \le m,n \le 2000)\) 表示两个张量的维度。

第二行 \(m\) 个整数 \(p_1,p_2,\cdots,p_m (1 \le p_i \le 2000)\) 描述第一个张量每个维度的长度。

第三行 \(n\) 个整数 \(q_1,q_2,\cdots,q_n (1 \le q_i \le 2000)\) 描述第二个张量每个维度的长度。

输出格式

输入一行一个整数表示最少的插入 \(1\) 的数量使得两个张量变为可广播的。

样例 #1

样例输入 #1

4 2
2 1 3 2
4 2

样例输出 #1

1

提示

在序列 \(q\) 的第二个位置之前插入一个 \(1\)(得到 4 1 2),两个张量就会变为可广播的。

题解

题目中描述的比较复杂,其实我们只要把两个数组翻转过来就可以了,这样这个题的本质是通过插入1来实现配对,\(f[i][j]表示A串的前i个和B串的前j个匹配好的最小代价,如果在第i个位置添加1,那么这个1一定适合b[j]进行匹配,f[i][j]=f[i][j-1]+1,如果在第j个位置添加1,那么这个1一定是和a[i]进行匹配,所以f[i][j]=f[i-1][j]+1,这个题的状态转移就完成了,最终答案是什么呢?min(f[i][m],f[n][i])\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2005;
int m,n,a[maxn],b[maxn],f[maxn][maxn],ans=2005;
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        scanf("%d",&b[i]);
    reverse(a+1,a+1+m);
    reverse(b+1,b+1+n);
    if(m>n){
        swap(m,n);
        swap(a,b);
    }
    a[m+1]=-1;
    b[n+2]=-2;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=2005;
    f[0][0]=0;
    for(int i=1;i<=n;i++)
        f[0][i]=i;
    for(int i=1;i<=m;i++)
        f[i][0]=i;
    
    for(int i=1;i<=m+1;i++)
        for(int j=1;j<=n+1;j++){
            if(a[i]==b[j]||(a[i]==1||b[j]==1)) f[i][j]=f[i-1][j-1];
            else{
                f[i][j]=min(f[i][j],f[i][j-1]+1);
                f[i][j]=min(f[i][j],f[i-1][j]+1);
            }
        }
    for(int i=0;i<=n;i++){
            ans=min(ans,f[m][i]);
    }
    for(int i=0;i<=m;i++){
            ans=min(ans,f[i][n]);
    }
    
    cout<<ans<<endl;
    return 0;
    
}

T3 回文

题目描述

从前有一个萌萌哒的由小写字母构成的字符串S。但它觉得如果自己不是回文串的话就非常无聊,所以它想要添加一些字符,使自己变成回文串。
添加每种字符都会有“1”的代价。
它想知道将自己变成回文串的最小代价。但因为字符串不会写代码,所以它把这个任务交给了你。

题解

这个题是通过添加字符的方式,使得字符串变成回文串,我们知道回文一定是一个区域,所以我们设\(f[i][j]表示使得i和j之间变成回文添加字符的最小代价,既然是区域问题,肯定是由小变大,所以这个类似于区间dp的形式,假设a[i]==b[j],那么f[i][j]=f[i+1][j-1],如果a[i]!=b[j]呢?这个时候就需要通过添加的方式使其变成回文,无非是需要在i和j两个位置添加,如果在j这个位置添加,那么i+1到j的位置已经构成回文,j位置添加的字符和a[i]构成回文,如果在i这个位置添加,那么f[i][j]=f[i][j-1]+1,也就是说在第i个位置添加的是a[j],这样,这个题就做完了\)

修改

如果我们在这个题中加入了删除操作,该怎么弄?
\(本质上删除操作,其实和插入操作大体一致,假设删除a[i],则i+1到j构成回文,假设删除a[j],则i到j-1构成回文\)

相关的题目是oj 回文2(辽宁考试)

posted @ 2024-03-20 21:56  xinyimama  阅读(19)  评论(0编辑  收藏  举报