LCS + LIS

参考:

https://www.cnblogs.com/chenleideblog/p/10455723.html

https://blog.csdn.net/qq_25800311/article/details/81607168

https://blog.csdn.net/lxt_Lucia/article/details/81206439

 

 

一,概念

1,LCS

  最长公共子序列(Longest Common Sequence)和最长公共子串(Longest Common Substring)的英文缩写皆为 LCS。

  两者的区别在于:

    子序列不需要在原序列中占用连续的位置。而最长公共子串要求连续。

2,LIS

  最长上升子序列(Longest  Increasing Subsequence)

 

 

二,最长公共子串

1,推导

  有字符串 s1 长度为 n,字符串 s2 长度为 m

    设  dp[ i ][ j ] 代表:s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 的最长公共后缀的长度,

      即如果 dp[ i ][ j ] 不为 0,则 s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 存在一个公共子串,其最后一个字符为 s1[ i-1 ]。

    因为最长公共子串要求连续,所以 dp[ i+1 ][ j+1 ] 只由 dp[ i ][ j ] 和 s[ i ] 和 s[ j ] 决定。

    所以有递推式(注意:0 <= i < n,0 <= j < m ):

      if s1[ i ] == s2[ j ]

        dp[ i+1 ][ j+1 ] = dp[ i ][ j ] + 1

      else

        dp[ i+1 ][ j+1 ] = 0

    所以有递推初始条件:

      dp[ 0 ][ 0~m ] = 0;dp[ 0~n ][ 0 ] = 0

2,例题

  链接:http://poj.org/problem?id=2217

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 5005
char s1[N], s2[N];
int dp[N][N];
void getLCS()
{
    int n = strlen(s1), m = strlen(s2);
    int max = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (s1[i] == s2[j])
                dp[i + 1][j + 1] = dp[i][j] + 1;
            else
                dp[i + 1][j + 1] = 0;
            if (dp[i + 1][j + 1] > max)
                max = dp[i + 1][j + 1];
        }
    }
    printf("Nejdelsi spolecny retezec ma delku %d.\n", max);
}
int main(void)
{
    int t; scanf("%d", &t);
    getchar();
    while(t--)
    {
        scanf("%[^\n]", s1); getchar();
        scanf("%[^\n]", s2); getchar();
        getLCS();
    }
    system("pause");
    return 0;
}
View Code

 

 

 

三,最长公共子序列

1,推导

  有字符串 s1 长度为 n,字符串 s2 长度为 m

    设  dp[ i ][ j ] 代表:

      s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 的最长公共子序列的长度。

    因为 dp[ i ][ j ] 是当前最长的值,所以,如果 s1[ i ] == s2[ j ],则 dp[ i ][ j ] + 1 必然是 s1[ 0~i ] 和 s2[ 0~j ] 的最长公共子序列的长度。

    因为子序列不要求连续,所以 dp] i+1 ][ j+1 ]  并不要求其 LCS 以 s[ i ] 或 s[ j ] 结尾,它是包含着 s1[ 0~i-1 ] 和 s2[ 0~j-1 ] 的所有公共子序列中长度最长的一个。

    所以,当 s1[ i ] != s2[ j ] 时,dp[ i+1 ][ j+1 ] 不能等于 0,它必须等于它之前的最长子序列。

      而 dp[ i+1 ][ j+1 ]  之前的最长子序列长度为:不包括 dp[ i+1 ][ j+1 ] 的 dp[ 0~i+1 ][ 0~j+1 ] 。

      而 dp[ i+1 ][ j ] 就代表了 dp[ 0~i+1 ][ 0~j ] 的最大值,dp[ i ][ j+1 ] 就代表了 dp[ 0~i ][ 0~j+1 ] 的最大值。

        显然,两者的最大值就是 dp[ i+1 ][ j+1 ] 之前的最长子序列的长度。      

    所以有递推式(注意:0 <= i < n,1 <= j < m ):

      if s1[ i ] == s2[ j ]

        dp[ i+1 ][ j+1 ] = dp[ i ][ j ] + 1

      else

        dp[ i+1 ][ j+1 ] = MAX( dp[ i ][ j+1 ] ,  dp[ i+1 ][ j ] )

2,例题

  链接:http://poj.org/problem?id=1458

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 1000+5
char s1[N], s2[N];
int dp[N][N];
void getLCS()
{
    int n = strlen(s1), m = strlen(s2);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
            if (s1[i] == s2[j])
                dp[i + 1][j + 1] = dp[i][j] + 1;
            else
                dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]);

    }
    printf("%d\n", dp[n][m]);
}
int main(void)
{
    while (scanf("%s%s", s1, s2) != EOF)
    {
        memset(dp, 0, sizeof(dp));
        getLCS();
    }
    system("pause");
    return 0;
}
View Code

 

3,最长公共子序列的输出

  定于一个用于记录的数组 rs,其中 f[ i ][ j ] 代表 dp[ i ][ j ] 是由哪一个值推出来的。即

    当 f[ i ][ j ] == 1 时,dp[ i ][ j ] 等于 dp[ i-1 ][ j-1 ] + 1;

    当 f[ i ][ j ] == 2 时,dp[ i ][ j ] 等于 dp[ i ][ j-1 ];

    当 f[ i ][ j ] == 2 时,dp[ i ][ j ] 等于 dp[ i-1 ][ j ];

  因为只有 f[ i ][ j ] == 1 时,s1[ i-1 ] 才会等于 s2[ j-1 ],此时的 s1[ i-1 ] 或 s2[ j-1 ] 才是属于最长公共子序列的。

  所以,要想输出 LCS,

    就先利用 rs 的标志作用从 f[ n ][ m ] 往前搜索,搜索到 f 数组的行下标为 0 或列下标为 0 为止。

    然后在回溯的时候,只输出 f[ i ][ j ] == 1 时,对应的 s1[ i-1 ] 的值,就可以得到 LCS 了。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 1000+5
char s1[N], s2[N];
int dp[N][N], f[N][N];
void show(int i, int j)  // 回溯输出子序列
{
    if (i <= 0 || j <= 0)
        return;
    if (f[i][j] == 1)
    {
        show(i - 1, j - 1);
        printf("%c ", s1[i - 1]);
    }
    if (f[i][j] == 2)
        show(i, j - 1);
    if (f[i][j] == 3)
        show(i - 1, j);
}
void getLCS()
{
    int n = strlen(s1), m = strlen(s2);
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (s1[i - 1] == s2[j - 1])
            {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                f[i][j] = 1;
            }
            else if (dp[i][j - 1] >= dp[i - 1][j])
            {
                dp[i][j] = dp[i][j - 1];
                f[i][j] = 2;
            }
            else
            {
                dp[i][j] = dp[i - 1][j];
                f[i][j] = 3;
            }
        }
    }
    printf("%d\n", dp[n][m]);
    show(n, m);
    puts("");
}
int main(void)
{
    while (scanf("%s%s", s1, s2) != EOF)
    {
        memset(dp, 0, sizeof(dp));
        memset(f, 0, sizeof(f));
        getLCS();
    }
    system("pause");
    return 0;
}
View Code

 

 

 

四,最长上升子序列

1,推导

  设数组 a 长度为 n,求 a[] 的 LIS。

    设 f[ i ] 代表:

      以 a[ i ] 为最大值的最长上升子序列的长度。

    因为是子序列,不要求连续,所以如果要求 f[ i ] 的值,

      那么 a[ 0~i-1 ] 中任一取 a[ j ],满足 a[ j ] < a[ i ],

        则 以 a[ j ] 为最大值的最长上升子序列 可以和 a[ j ] 组成一个新的上升序列,是以 a[ i ] 为最大值的。

        原则上,当 j 离 i 越近,f[ j ] 就应该越大;当 a[ j ] 离 a[ i ] 越近,则 f[ j ] 就应该越大。如果我们能直接定位最大 f[ j ] 的值,则 f[ i ] 就等于 f[ j ] + 1

        但显然,我们无法综合上述两种指标,所以只有将所有的 f[ j ] 都遍历一遍,取最大值即为 f[ i ]。

    而如果找不到任何一个 a[ j ] 比 a[ i ] 小,说明该上升序列只有它本身,则 f[ i ] = 1。

    所以有递推式:

      if a[ j ] < a[ i ]

        f [ i ] = max( f[ j ] + 1, f[ i ]),j 在 [0, i-1] 区间循环

    递推初始条件:

      f[ i ] = 1,0 <= i < n

2,例题

  链接:http://acm.hdu.edu.cn/showproblem.php?pid=1257

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 100000+5
#define inf 0x3f3f3f3f
int a[N], f[N];
void getLIS(int n)
{
    int rs = -inf;
    for (int i = 0; i < n; i++)
    {
        f[i] = 1;
        for (int j = 0; j < i; j++)
            if (a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
        rs = max(rs, f[i]);
    }
    printf("%d\n", rs);
}
int main(void)
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        memset(f, 0, sizeof(f));
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        getLIS(n);
    }
    system("pause");
    return 0;
}
View Code

 

 

 

=========== ========= ======== ======= ====== ===== ==== === == =

     颂     无门和尚(宋)

春有百花秋有月,夏有凉风东有雪。

若无闲事挂心头,便是人间好时节。

 

posted @ 2020-02-22 12:11  叫我妖道  阅读(376)  评论(0编辑  收藏  举报
~~加载中~~