1. 问题背景
在学习LCS之前,我们需要了解几个概念:
(1)子序列:设X=(x1, x2, … xm) 是一个序列, W = (w1, w2, … wm) 是 X 的一个子序列,如果存在1≤i1<i2<…<ik<m, 使xi j= wj 。 也就是说W = (w1, w2, … wm) 即是X=(x1, x2, … xm)中删除一些元素以后得到的序列。
(2)公共子序列:W是序列X与Y的公共子序列,如果W是X的子序列且W是Y的子序列。
2. 问题描述(LCS):
输入:X=(x1, x2, … xn),Y=(y1, y2, …ym)
输出:X与Y的最长公共子序列W
3. 题解:
我们先进行一下最长公共子序列优化解结构分析,如下图:
如果理解了上述定理,我们就可以很容易的写出规划方程:
我们可以把i=0或j=0的情况视为边界条件,即最开始序列中没有元素的时候LCS肯定为0。我们通过下面的例子来深入了解一下此题的dp思想:
图中的箭头是存储这个数值是从哪个子问题衍生出来的,假如我们不仅仅要求出LCS的长度,还要求出LCS中的元素,即最长公共子序列究竟是什么的话,就需要有一个备忘录,来记录表格中的数值是从哪个子问题衍生出来的,就可以得到LCS。当然,答案可能是不唯一的,比如最后一个(最右下角)元素4,根据我们的规划方程,B≠A,即C[7,6] = max(C[6,6], C[7,5]),而显然会发现这两个值都是4,也就是memo中记录👆或者👈都可以,这就会因程序员定义的优先级不同而使LCS序列不同。
4. 代码:
//
// Created by 23011 on 4/4/2022.
//
//最长公共子序列
#include<cstdio>
#include <iostream>
#include<string>
#include<cstring>
#define MAX 1001
using namespace std;
int dp[MAX][MAX];
int main()
{
int n;
scanf("%d", &n);
while(n--)
{
string a,b;
cin >> a >> b;
memset(dp,0,sizeof(dp));
int len_a = a.size(), len_b = b.size();
for(int i = 0;i < len_a; i++)
{
for(int j = 0; j < len_b; j++)
{
if(a.at(i) == b.at(j))
dp[i + 1][j + 1] = dp[i][j] + 1;
else
dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
}
}
printf("%d\n",dp[len_a][len_b]);
a.clear();
b.clear();
}
return 0;
}