8.17做题随记
P2758 编辑距离
题目描述
设 \(A\) 和 \(B\) 是两个字符串。我们要用最少的字符操作次数,将字符串 \(A\) 转换为字符串 \(B\)。这里所说的字符操作共有三种:
- 删除一个字符;
- 插入一个字符;
- 将一个字符改为另一个字符。
\(A, B\) 均只包含小写字母。
输入格式
第一行为字符串 \(A\);第二行为字符串 \(B\);字符串 \(A, B\) 的长度均小于 \(2000\)。
输出格式
只有一个正整数,为最少字符操作次数。
输入输出样例 #1
输入 #1
sfdqxbw
gfdgw
输出 #1
4
说明/提示
对于 \(100 \%\) 的数据,\(1 \le |A|, |B| \le 2000\)。
分析
这道题的唯一难点在于想清楚状态 $ dp[i][j]:到A[i],B[j]时的最少操作次数 $
接下来
1:no moving
dpa dp[i][j]=dp[i-1][j-1]
dpa
2:erase
dpa dp[i][j]=dp[i-1][j]+1
dp
3:add
dp dp[i][j]=dp[i][j-1]+1
dpa
4:change
dpa dp[i][j]=dp[i-1][j-1]+1
dpb
之前的错误:dp时逻辑错误
代码
#include<bits/stdc++.h>
using namespace std;
const int U=2e3+5; // 定义动态规划数组的最大尺寸
typedef long long ll;
string A,B; // 输入的两个字符串
int dp[U][U]; // DP数组,dp[i][j]表示A前i字符转B前j字符的最小操作数
// 调试函数:打印整个DP数组
void debug()
{
for(int i=0;i<=A.size();i++)
for(int j=0;j<=B.size();j++)
printf("dp[%d][%d]:%d\n",i,j,dp[i][j]);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin); // 文件输入(仅调试时使用)
freopen("c.out","w",stdout); // 文件输出(仅调试时使用)
#endif
// 输入两个字符串
cin>>A>>B;
// 初始化边界条件:
// 1. A前i字符转空串需要i次删除操作
for(int i=0;i<=A.size();i++) dp[i][0]=i;
// 2. 空串转B前j字符需要j次插入操作
for(int i=0;i<=B.size();i++) dp[0][i]=i;
// 动态规划填表
for(int i=1;i<=A.size();i++)
for(int j=1;j<=B.size();j++)
{
// 情况1:当前字符相同,无需操作
if(A[i-1]==B[j-1]) dp[i][j]=dp[i-1][j-1];
// 情况2:字符不同,取三种操作的最小值
else dp[i][j]=min({
dp[i-1][j]+1, // 删除A[i]字符
dp[i][j-1]+1, // 插入B[j]字符
dp[i-1][j-1]+1 // 替换A[i]为B[j]
});
}
// 输出最终结果:A转B的最小操作数
cout<<dp[A.size()][B.size()]<<endl;
// debug(); // 调试时可取消注释查看DP表
return 0;
}
P1439 【模板】最长公共子序列
题目描述
给出 \(1,2,\ldots,n\) 的两个排列 \(P_1\) 和 \(P_2\) ,求它们的最长公共子序列。
输入格式
第一行是一个数 \(n\)。
接下来两行,每行为 \(n\) 个数,为自然数 \(1,2,\ldots,n\) 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
输入输出样例 #1
输入 #1
5
3 2 1 4 5
1 2 3 4 5
输出 #1
3
说明/提示
- 对于 \(50\%\) 的数据, \(n \le 10^3\);
- 对于 \(100\%\) 的数据, \(n \le 10^5\)。
分析
这道题有两个难点
如何转化成最长上升子序列的问题
这里有一个神操作,因为A,B都是排列组合1-n,所以就可以使用标号法
将数组A转化成1-n的序列,那么B数组只要是选择最长上升子序列,就一定是最长公共子序列(不易想到)
注意了,标号操作长这样:
for(int i=1;i<=n;i++) cin>>A[i],ranks[A[i]]=i;
for(int j=1;j<=n;j++) cin>>B[j];
for(int i=1;i<=n;i++) rb[i]=ranks[B[i]];
如何解决最长上升子序列的问题
这个问题将放一段代码,明日会写一篇文章解答
经典例题
int ans=0;
for(int i=1;i<=n;i++)
{
auto it=upper_bound(f+1,f+ans+1,rb[i]);
int x=it-f;
ans=max(ans,x);
f[x]=rb[i];
}
代码
#include<bits/stdc++.h>
using namespace std;
const int U=1e5+5; // 数组最大长度,1e5是科学计数法表示100000
typedef long long ll; // 定义ll为long long类型别名
int n; // 序列长度
int A[U],B[U],ranks[U],rb[U]; // A和B是输入序列,ranks记录值的位置,rb存储B在A中的位置
int f[U]; // 用于计算最长递增子序列的辅助数组
int main()
{
#ifndef ONLINE_JUDGE // 条件编译,用于文件输入输出
freopen("c.in","r",stdin); // 从文件读取输入
freopen("c.out","w",stdout); // 输出到文件
#endif
cin>>n; // 读取序列长度n
// 读取序列A,并建立值到位置的映射
for(int i=1;i<=n;i++) cin>>A[i],ranks[A[i]]=i; // ranks[值]=在A中的位置
// 读取序列B
for(int j=1;j<=n;j++) cin>>B[j];
// 将B序列转换为在A中的位置序列
for(int i=1;i<=n;i++) rb[i]=ranks[B[i]]; // rb[i]表示B[i]在A中的位置
// 计算最长递增子序列长度
int ans=0; // 初始化最长递增子序列长度为0
for(int i=1;i<=n;i++) // 遍历rb数组
{
// 使用upper_bound在f数组中查找插入位置
auto it=upper_bound(f+1,f+ans+1,rb[i]);
int x=it-f; // 计算应该插入的位置
ans=max(ans,x); // 更新最大长度
f[x]=rb[i]; // 维护f数组
}
cout<<ans<<endl; // 输出结果
return 0;
}

浙公网安备 33010602011771号