8.17做题随记

P2758 编辑距离

题目描述

\(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[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;
}
posted @ 2025-08-17 22:06  左边之上  阅读(8)  评论(0)    收藏  举报