315. 旅行(lcs,统计lcs个数并输出)

分析:

  1.  先求出lcs的长度

  2. dfs处理两个子串,找出所有lcs方案并且输出

lcs的求法

最大公共子序列问题_qq12323qweeqwe的博客-CSDN博客

状态计算:可以分为不重不漏的四个子集

  • a[i]在,b[j]在        dp[i-1][j-1]+1
  • a[i]在,b[j]不在    dp[i][j-1]
  • a[i]不在,b[j]在    dp[i-1][j]
  • a[i]不在,b[j]不在 dp[i-1][j-1]

寻找lcs方案

由于我们已经知道了lcs是多少了,那么寻找lcs方案就方便得多了,只要dfs暴力遍历一遍,然后和状态表示图一样,增加相应的字符就行了

暴力枚举,会T

#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
using namespace std;
const int N = 105;
char a[N];
char b[N];
int dp[N][N];
int res;
int la, lb;
set<string> s;
void dfs(int i, int j, int now, string str)
{
    if (now==res)
    {
        s.insert(str);
        return;
    }
    if (i > la || j > lb)
        return;
    
    if (a[i] == b[j])
        dfs(i + 1, j + 1, now + 1, str + a[i]);
    dfs(i + 1, j, now, str );
    dfs(i, j + 1, now, str );
}
int main()
{
    cin >> a + 1;
    cin >> b + 1;
     la = strlen(a+1);
     lb = strlen(b+1);

    for (int i = 1;i <= la;i++)
        for (int j = 1;j <= lb;j++)
        {
            dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            if (a[i] == b[j])
                dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
        }
    res = dp[la][lb];

    dfs(1, 1, 0, "");

    for (auto t : s)
    {
        cout << t << '\n';
    }

    return 0;
}

预处理优化

由于使用暴力枚举会tle,我们要寻找一种优化

发现,其实能够影响dfs的形参now和str的只有a[i]==b[j]的情况

那么我们只要保存a[i]==b[j],就可以极大限度的剪枝

由于有26个字母,一个最长公共子序列每个位置上的字母未知,那么就用26个字母枚举一遍各个位置可能的字母,如果这种情况可能就继续dfs,否则就跳过

首先需要对两个字符串进行预处理

  • pa[j][i]        字母 char('a'+j) 在字符串1~i中最后出现的位置
  • pb[j][i]        字母 char('a'+j) 在字符串1~i中最后出现的位置
for(int i=1;i<=la;i++)
        for (int j = 0;j < 26;j++)
        {
            if (a[i] == 'a' + j) pa[j][i] = i;
            else pa[j][i] = pa[j][i - 1];
        }

    for (int i = 1;i <= lb;i++)
        for (int j = 0;j < 26;j++)
        {
            if (b[i] == 'a' + j) pb[j][i] = i;
            else pb[j][i] = pb[j][i - 1];
        }

dfs

void dfs(int i, int j, int now, string str)
{
    if (!now)    //满足条件存入
    {
        s.insert(str);
        return;
    }
    if (!i || !j)    //越界跳过
        return;
    else
    {
        for (int k = 0;k < 26;k++)
        {    //记录第k个字母在1~i,1~j中最后一次出现的位置
            int pi = pa[k][i], pj = pb[k][j];
            if (dp[pi][pj] == now)    //如果最后一次出现的位置是lcs第now个字母,继续递归
                dfs(pi - 1, pj - 1, now - 1, char('a' + k) + str);
                //直接跳过pi,pj前面递归
        }
    }

}

完整ac代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
using namespace std;
const int N = 105;
char a[N];
char b[N];
int dp[N][N];
int res;
int la, lb;
int pa[N][N];
int pb[N][N];
set<string> s;
void dfs(int i, int j, int now, string str)
{
    if (!now)
    {
        s.insert(str);
        return;
    }
    if (!i || !j)
        return;
    else
    {
        for (int k = 0;k < 26;k++)
        {
            int pi = pa[k][i], pj = pb[k][j];
            if (dp[pi][pj] == now)
                dfs(pi - 1, pj - 1, now - 1, char('a' + k) + str);
        }
    }

}
int main()
{
    cin >> a + 1;
    cin >> b + 1;
     la = strlen(a+1);
     lb = strlen(b+1);

    for (int i = 1;i <= la;i++)
        for (int j = 1;j <= lb;j++)
        {
            dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            if (a[i] == b[j])
                dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
        }
    res = dp[la][lb];
    for(int i=1;i<=la;i++)
        for (int j = 0;j < 26;j++)
        {
            if (a[i] == 'a' + j) pa[j][i] = i;
            else pa[j][i] = pa[j][i - 1];
        }
    for (int i = 1;i <= lb;i++)
        for (int j = 0;j < 26;j++)
        {
            if (b[i] == 'a' + j) pb[j][i] = i;
            else pb[j][i] = pb[j][i - 1];
        }
    dfs(la, lb, res, "");
    for (auto t : s)
        cout << t << '\n';
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值