• LCS


    博客图片

    问题描述

            从一个序列中删去某些元素可以得到一个子序列,对于序列XY,如果序列Z既是X的子序列又是Y的子序列,那么称ZXY的公共子序列,长度最长的子序列称为最长公共子序列LCS.给定两个序列XY,计算的最长公共子序列的长度,输出一种可行的方案.

    分析

            对于序列XY的公共子列来说,如果它们最后一个字母一样,那么它们的LCS一定是去掉最后一个字母的两个序列的LCS的长度加一;否则就是X去掉最后一个字母的序列和序列YLCS的长度与序列Y去掉最后一个字母的序列与序列XLCS的长度的最大值,所以对于长度为i的序列X和长度为j的序列Y,它们的LCS的长度满足:

    [dp[i][j]= egin{cases} dp[i-1][j-1], qquad X[i]==Y[j]\ max(dp[i-1][j],dp[i][j-1]) quad X[i]\, !=Y[j] end{cases} ]

    然后可以用一个两重循环计算出结果,最后的dp[i][j]就是长度为i的序列X和长度为j的序列YLCS的长度,对应的代码如下:

        void solve(const string & a, const string& b){
            memset(dp,0,sizeof(dp));
            for(int i = 1;i <= a.length(); ++i){
                for(int j = 1;j <= b.length(); ++j){
                    if(a[i-1] == b[j-1]){
                        dp[i][j] = dp[i-1][j-1]+1;
                    }else{
                        dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                    }
                }
            }
            printf("%lld
    ", dp[a.length()][b.length()]);
        }
    

            路径的打印需要从后向前看,判断dp[i][j]==dp[i-1][j]dp[i][j]==dp[i][j-1],相等表示此时的LCS(X_{i-1},Y_j)(X_i,Y_{j-1})构成的LCS长度相同,它们的LCS应该从这两个中选出;如果dp[i][j]==dp[i-1][j]+1表明从(X_{i-1},Y_j)(X_i,Y_i)LCS,X[i]==Y[j],X[i]被选中作为LCS,然后序列X缩短一个单位,去寻找下一个字符;如果dp[i][j]==dp[i][j-1]+1说明从(X_i,Y_{j-1})转向(X_i,Y_j),Y[j]LCS贡献了一个字符,此时的X[i]Y[j]是相等的.后面两种情况对应求长度时X[i]==Y[j]的情况,但是dp[i][j]==dp[i-1][j],dp[i][j]==dp[i][j-1],dp[i][j]==dp[i-1][j]+1,dp[i][j]==dp[i]总是满足其中的两个,所以最后求出的LCS一定只是其中的一个可行的方案.

        void print_path(const string & a, const string& b){
            string ans;
            for (int i = a.length(), j = b.length(); i > 0 && j > 0; ){
                if( dp[i][j] == dp[i-1][j]){
                    --i;
                }else if( dp[i][j] == dp[i][j-1]){
                    --j;
                }else if(dp[i][j] == dp[i-1][j]+1){
                    ans += a[i - 1];
                    --i;
                }else{
                    ans += b[j - 1];
                    --j;
                }
            }
            reverse(ans.begin(), ans.end());
            cout << ans << endl;
        }
    

    有一点需要注意的是,这样得到的LCS序列是一个逆序的,最后要经过一个倒置得到的才是要求的LCS的序列.

            从上面的dp数组可以看出,坐标i可以认为表示的是已经处理的序列X的长度,j表示已经处理的序列Y的长度,某一时刻的dp[i][j]仅仅和三个状态相关,分别是dp[i-1][j-1],dp[i-1][j],dp[i][j-1],如果列表来看的话,恰好是正上方正左面和左上角的三个方向,而此前的i-2,i-3,...很明显则与i无关了,所以可以对上面的程序稍加改动,得到一个滚动数组表示的:

        void LCS(const string & a, const string& b){
            ll ans[2][N] = {0};
            int pre = 1, cur = 0;
            for(int i = 1; i <= a.length(); ++i){
                swap(cur, pre);
                for (int j = 1; j <= b.length(); ++j){
                    if( a[i-1] == b[j-1] )
                        ans[cur][j] = ans[pre][j-1] + 1;
                    else
                        ans[cur][j] = max(ans[cur][j-1], ans[pre][j]);
                }
            }
            printf("%lld
    ", ans[cur][b.length()]);
        }
    

    用一个dp[2][N]进行滚动,一行表示处理序列Y一轮,一个记录的是上轮的结果,一个是当前的结果,没经过依次,交换curpre的值,用这次的覆盖上上次的,最后cur哪行的就是最后依次的结果.

    题目链接

    hdu1159

    题目概述

            给出一个序列X和序列Y,从序列中按下标严格递增((i_{1}<i_{2}<cdots<i_{k}))找出一个子序列,这个子序列也是序列Y的子序列,即,(a_{i_k}=b_{j_m},i_{k-1}<i_k, j_{m-1}<j_m),计算这样一个最长的子列的长度.

    代码实现

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1105;
    ll dp[N][N];
    
    
    void solve(const string & a, const string& b){
        memset(dp,0,sizeof(dp));
        for(int i = 1;i <= a.length(); ++i){
            for(int j = 1;j <= b.length(); ++j){
                if(a[i-1] == b[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }else{
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        printf("%lld
    ", dp[a.length()][b.length()]);
    }
    
    
    void LCS(const string & a, const string& b){
        ll ans[2][N] = {0};
        int pre = 1, cur = 0;
        for(int i = 1; i <= a.length(); ++i){
            swap(cur, pre);
            for (int j = 1; j <= b.length(); ++j){
                if( a[i-1] == b[j-1] )
                    ans[cur][j] = ans[pre][j-1] + 1;
                else
                    ans[cur][j] = max(ans[cur][j-1], ans[pre][j]);
            }
        }
        printf("%lld
    ", ans[cur][b.length()]);
    }
    
    void print_path(const string & a, const string& b){
        string ans;
        for (int i = a.length(), j = b.length(); i > 0 && j > 0; ){
            if( dp[i][j] == dp[i-1][j]){
                --i;
            }else if( dp[i][j] == dp[i][j-1]){
                --j;
            }else if(dp[i][j] == dp[i-1][j]+1){
                ans += a[i - 1];
                --i;
            }else{
                ans += b[j - 1];
                --j;
            }
        }
        reverse(ans.begin(), ans.end());
        cout << ans << endl;
    }
    
    int main(int argc, const char** argv) {
        string a, b;
        while(cin>>a>>b){
            // solve(a, b);
            // print_path(a, b);
            LCS(a, b);
        }
        return 0;
    }
    

    其它

  • 相关阅读:
    Entity Framework 教程
    C# yield
    表达式树系列文章汇总
    C#中静态与非静态方法比较
    谈谈对Spring IOC的理解
    工厂方法模式与IoC/DI
    通过配置的方式Autofac 《第三篇》
    Autofac 组件、服务、自动装配 《第二篇》
    Autofac 解释第一个例子 《第一篇》
    SQL Server索引调优系列
  • 原文地址:https://www.cnblogs.com/2018slgys/p/13371098.html
Copyright © 2020-2023  润新知