题目链接:
HDU 1503 Advanced Fruits
题意:
给出两个水果的名称字符串,字符个数不超过
100
,要求输出一个合并的名称,两个水果的名字都是合并的名称的子序列,输出合并后的名称,多解时输出任意解。
分析:
主要是找出最长公共子序列所选的字母在第一个串中或第二个串中的位置。而且这些字母都是相同的,根据
dp[]
状态转移方程:
s1[i]=s2[j]时:dp[i][j]=dp[i−1][j−1]+1
s1[i]!=s2[j]时:dp[i][j]=max(dp[i−1][j],dp[i][j−1])
实际上我们需要记录的位置只是第一种情况时的位置。另外我们还需要用 dp[i][j][0]和dp[i][j][1] 分别表示第一个串的前 i 个字符和第二个串的前
这样子我们在回溯的时候需要倒着寻找哪些字母被用上了。用 Max[i]表示所有dp[i][j][0]和dp[i][j][1] 的最大值。如果第 i 个字母被用上了,那么*
上面的是我的做法,看起来比较烦。。。可能描述的也不是很清楚。
到网上看别人的题解时发现 dfs 大法好啊。
其实想的时候是想用 dfs 回溯来着,只是没想到如何 dfs 。
可以用 pre[i][j] 来记录 dp[i][j] 状态转移时是从哪种状态转移过来的,一共就三种: dp[i−1][j−1]+1,dp[i−1][j],dp[i][j−1] 。然后从 dp[len0][len1] 开始往前递归,遇到从 dp[i−1][j−1]+1 状态转移的情况就标记,知道递归到 dp[0][]或者dp[][0] 结束。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 110;
char s[2][MAX_N];
int dp[MAX_N][MAX_N][2], match[MAX_N][MAX_N], vis[MAX_N], Max[MAX_N];
void solve()
{
memset(match, -1, sizeof(match));
memset(vis, 0, sizeof(vis));
memset(dp, 0, sizeof(dp));
memset(Max, -1, sizeof(Max));
int len0 = strlen(s[0] + 1), len1 = strlen(s[1] + 1);
for(int i = 1; i <= len0; ++i) {
for (int j = 1; j <= len1; ++j) {
if(s[0][i] == s[1][j]) {
dp[i][j][0] = max(dp[i - 1][j - 1][0], dp[i - 1][j - 1][1]) + 1;
match[i][j] = j;
} else {
dp[i][j][1] = max(dp[i - 1][j][0], dp[i - 1][j][1]);
dp[i][j][1] = max(dp[i][j][1], dp[i][j - 1][0]);
dp[i][j][1] = max(dp[i][j][1], dp[i][j - 1][1]);
}
}
}
int ed = len1;
for(int i = len0; i >= 1; --i) {
for(int j = ed; j >= 1; --j) {
Max[i] = max(Max[i], max(dp[i][j][0], dp[i][j][1]));
}
for(int j = ed; j >= 1; --j) {
if(dp[i][j][0] == Max[i]) {
vis[match[i][j]] = 1;
ed = match[i][j] - 1;
break;
}
}
}
int st = 1;
for(int i = 1; i <= len1; ++i) {
if(vis[i]) {
for(int j = st; j <= len0; ++j) {
if(s[0][j] == s[1][i]) {
st = j + 1;
break;
}
printf("%c", s[0][j]);
}
}
printf("%c", s[1][i]);
}
for(int i = st; i <= len0; ++i) {
printf("%c", s[0][i]);
}
printf("\n");
}
int main()
{
while(~scanf("%s%s", s[0] + 1, s[1] + 1)) {
solve();
}
return 0;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 110;
char s1[MAX_N], s2[MAX_N];
int dp[MAX_N][MAX_N], pre[MAX_N][MAX_N], vis[MAX_N];
int len1, len2;
void dfs(int p, int q)
{
if(p == 0 || q == 0) return ;
if(pre[p][q] == 0) {
vis[p] = 1; //记录第一个串中哪些位置被用掉
dfs(p - 1, q - 1);
} else if(pre[p][q] == 1) {
dfs(p, q - 1);
} else {
dfs(p - 1, q);
}
}
void solve()
{
memset(vis, 0, sizeof(vis));
memset(dp, 0, sizeof(dp));
memset(pre, -1, sizeof(pre));
len1 = strlen(s1 + 1), len2 = strlen(s2 + 1);
for(int i = 1; i <= len1; ++i) {
for(int j = 1; j <= len2; ++j) {
if(s1[i] == s2[j]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
pre[i][j] = 0;
} else {
if(dp[i][j - 1] > dp[i][j]) {
dp[i][j] = dp[i][j - 1];
pre[i][j] = 1;
}
if(dp[i - 1][j] > dp[i][j]) {
dp[i][j] = dp[i - 1][j];
pre[i][j] = 2;
}
}
}
}
dfs(len1, len2);
int st = 1;
for(int i = 1; i <= len1; ++i) {
if(vis[i] == 1) {
for(int j = st; j <= len2; ++j) {
if(s1[i] == s2[j]) {
st = j + 1;
break;
}
printf("%c", s2[j]);
}
}
printf("%c", s1[i]);
}
for(int j = st; j <= len2; ++j) {
printf("%c", s2[j]);
}
printf("\n");
}
int main()
{
while(~scanf("%s%s", s1 + 1, s2 + 1)) {
solve();
}
return 0;
}