HDU 1503 Advanced Fruits(还原最长公共子序列、记录路径)

31 篇文章 0 订阅
10 篇文章 0 订阅

题目链接:
HDU 1503 Advanced Fruits
题意:
给出两个水果的名称字符串,字符个数不超过 100 ,要求输出一个合并的名称,两个水果的名字都是合并的名称的子序列,输出合并后的名称,多解时输出任意解。
分析:
主要是找出最长公共子序列所选的字母在第一个串中或第二个串中的位置。而且这些字母都是相同的,根据 dp[] 状态转移方程:

s1[i]=s2[j]:dp[i][j]=dp[i1][j1]+1

s1[i]!=s2[j]:dp[i][j]=max(dp[i1][j],dp[i][j1])

实际上我们需要记录的位置只是第一种情况时的位置。另外我们还需要用 dp[i][j][0]dp[i][j][1] 分别表示第一个串的前 i 个字符和第二个串的前j个字符求最大公共字符列时第一个串的第 i 个字符有没有和第二个串的第j个字符匹配。
这样子我们在回溯的时候需要倒着寻找哪些字母被用上了。用 Max[i]dp[i][j][0]dp[i][j][1] 的最大值。如果第 i 个字母被用上了,那么*Max[i]dp[i][][0]中产生的。还要注意 Max[] 的范围应是后一个位置已确定匹配位置之前的所有位置。这样子就能确定第二个串中的哪些字母备用上了,用 vis[] 数组标记,再输出的时候先输出第二个字符串,如果输到某个字母被 vis[] 标记了,那么就开始输出第一个字符串知道第一个字符串的字母和当前第二个字符串的字母相同时跳出,接着输第二个字符串。
上面的是我的做法,看起来比较烦。。。可能描述的也不是很清楚。
到网上看别人的题解时发现 dfs 大法好啊。
其实想的时候是想用 dfs 回溯来着,只是没想到如何 dfs
可以用 pre[i][j] 来记录 dp[i][j] 状态转移时是从哪种状态转移过来的,一共就三种: dp[i1][j1]+1,dp[i1][j],dp[i][j1] 。然后从 dp[len0][len1] 开始往前递归,遇到从 dp[i1][j1]+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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值