前置芝士
- 会用dp
- LCS的概念及其转移方程
一句话题意:将两个字符串结合起来,他们的公共子串只输出一次
于是,我们想到了LCS(最长公共子序列)。对数组dp中的每一个数都进行标记,标记这个坐标的值是“从哪来的”。这里用一个v数组实现:
int v[110][110];
/*
(注:下文中,坐标(i,j)均表示dp[i][j]中的(i,j)二元组)
每个v[i][j]=x表示当前坐标为(i,j),由x号状态转移过来(x的具体意义如下)
如果x=0,说明当前状态是由坐标(i-1,j-1)转移过来的。(也就是dp[i][j]=dp[i-1][j-1]+1)
如果x=1,说明当前状态是由坐标(i,j-1)转移过来的。(也就是dp[i][j]=dp[i][j-1])
如果x=-1,说明当前状态是由坐标(i-1,j)转移过来的。(也就是dp[i][j]=d[i-1][j])
*/
为了方便输出路径中的字符,定义了一个函数print_path来递归找路径:
void print_path(int i,int j)
{
if(!i && !j) return; //到达了坐标(0,0),无需再递归,返回
if(v[i][j]==0)
{
print_path(i-1,j-1);
putchar(a[i-1]);
}
else if(v[i][j]==-1)
{
print_path(i-1,j);
putchar(a[i-1]);
}
else
{
print_path(i,j-1);
putchar(b[j-1]);
}
}
但是,如果递归最后的终点不是坐标(0,0)呢?不就会Runtime Error了吗?
于是,加了两行关键的初始化来控制边界:
for(int i=1;i<la;i++) v[i][0]=-1; //到达纵坐标的边界,这样处理使递归时不会再让j--
for(int i=1;i<lb;i++) v[0][i]=1; //同上,到达了横坐标的边界,使递归时不会再让i--
完整代码:
/*
Author: juruo_zzt
Judge Status: Accepted
Time: 15MS
Memory: 1468K
Code Length: 979B
*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[110][110],v[110][110];
char a[110],b[110];
void print_path(int i,int j)
{
if(!i && !j) return;
if(v[i][j]==0)
{
print_path(i-1,j-1);
putchar(a[i-1]);
}
else if(v[i][j]==-1)
{
print_path(i-1,j);
putchar(a[i-1]);
}
else
{
print_path(i,j-1);
putchar(b[j-1]);
}
}
int main()
{
while(~scanf("%s%s",a,b))
{
memset(dp,0,sizeof(dp));
int la=strlen(a),lb=strlen(b);
for(int i=1;i<la;i++) v[i][0]=-1;
for(int i=1;i<lb;i++) v[0][i]=1;
for(int i=1;i<=la;i++)
{
for(int j=1;j<=lb;j++)
{
if(a[i-1]==b[j-1])
{
v[i][j]=0;
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
if(dp[i][j-1]>dp[i-1][j])
{
v[i][j]=1;
dp[i][j]=dp[i][j-1];
}
else
{
v[i][j]=-1;
dp[i][j]=dp[i-1][j];
}
}
}
}
print_path(la,lb);
puts("");
}
return 0;
}