LIS和LCS的一些个人体会

LIS

俗名最长递增子序列,一个dp问题。
最简单的解法是时间复杂度O(n2)的解法,优化版的是二分的O(nlogn)解法。

若仅仅只是求长度

int LIS(vector<int>& arr) {
        // write code here
        int n=arr.size();
        vector<int>res;
        for(int i=0;i<n;i++)
        {
            auto it=lower_bound(res.begin(),res.end(),arr[i]);
            if(it==res.end())
            {
                res.push_back(arr[i]);
            }
            else
            {
                *it=arr[i];
            }
        }
        return res.size();
    }

若是要求求LIS的具体元素,就需要额外引进一个数组pos,用来标记LIS每个元素都是谁,然后根据这个pos数组,用一个指针等于LIS的长度,倒着遍历,遇到一个元素的pos值与p相等,说明LIS的第p个元素就是他,计入答案数组即可,别忘了记录完p--。

vector<int> LIS(vector<int>& arr) {
        int n=arr.size();
        int len=0;
        vector<int>pos(n);
        vector<int>dp;
        for(int i=0;i<n;i++)
        {
            auto it=lower_bound(dp.begin(),dp.end(),arr[i]);
            pos[i]=it-dp.begin();
            if(it==dp.end())
            {
                dp.push_back(arr[i]);
            }
            else
            {
                *it=arr[i];
            }
        }
        int p=dp.size()-1;
        vector<int> ans(p+1);
        for(int i=n-1;i>=0;i--)
        {
            if(p==pos[i])
            {
                 ans[p]=(arr[i]);
                p--;
            }
           
        }
        return ans;

LCS

又叫最长公共子序列,就是找到两个字符串的LCS。

1.求LCS的长度
把两个字符串看成一个二维矩阵,会发现这其实就是一个二维dp,如果s1[i-1]==s2[j-1],那么dp[i][j]就等于dp[i-1][j-1]+1,如果不相等就需要从这个dp[i][j]的上方或者左方去找一个较大值
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);这样计算下来dp[n][m]就是LCS的数值。

2.求LCS的具体字符串
需要在计算完dp[n][m]后,接着对两个字符串用两个指针倒序遍历,如果s[l-1]==s[r-1],那么说明这个字符就是我们想要的,用一个字符串收集起来。如果不相等呢,我们需要去看这个dp[l][r]上和左的情况,我们要去找更大的情况,举个例子如果左边大于上边,就需要r--。

string LCS(string s1, string s2) {
        int n = s1.size(), m = s2.size();
    vector<vector<int>> dp(n + 1, vector<int>(m + 1));
    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;
            }
            else
            {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    string str="";
        int l=n,r=m;
        while(l>0&&r>0)
        {
            if(s1[l-1]==s2[r-1])
            {
                str=s1[l-1]+str;
                l--;
                r--;
            }
            else if(dp[l-1][r]<dp[l][r-1])
            {
                r--;
            }
            else
            {
                l--;
            }
        }
       if(str=="")return "-1";
        else return str;
    }

3.求所有LCS字符串的个数

另外开辟一个c数组来计算方案数,如果dp[i][j]dp[i-1][j]那么dp[i-1][j]的方案就需要收集,如果dp[i][j]dp[i][j-1]那么dp[i][j-1]的方案就需要收集,如果同时收集了就说明dp[i-1][j-1]的方案收集了两次,需要再减去一次。

void solve()
{
    cin >> s >> t;
    n = s.size()-1, m = t.size()-1;
    dp.assign(n + 1, vi(m + 1, 0));
    vvi c(n + 1, vi(m + 1, 0));
    
    // 初始化
    for(int i=0; i<=n; i++) c[i][0] = 1;
    for(int i=0; i<=m; i++) c[0][i] = 1;

    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (s[i-1] == t[j-1])
            {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                // 当字符相等时,最长公共子序列必定包含这对字符
                // 方案数直接继承左上方
                // 题目要求的是不同位置的序列,所以要按如下方式处理:
                if(dp[i][j] == dp[i-1][j]) c[i][j] = (c[i][j] + c[i-1][j]) % mod;
                if(dp[i][j] == dp[i][j-1]) c[i][j] = (c[i][j] + c[i][j-1]) % mod;
            }
            else
            {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                // 方案数清零开始累加
                c[i][j] = 0; 
                if(dp[i][j] == dp[i-1][j]) c[i][j] = (c[i][j] + c[i-1][j]) % mod;
                if(dp[i][j] == dp[i][j-1]) c[i][j] = (c[i][j] + c[i][j-1]) % mod;
                
                // 容斥去重:如果左和上长度都等于当前最大长度,说明左上角被加了两遍
                if(dp[i][j] == dp[i-1][j-1])
                {
                    c[i][j] = (c[i][j] - c[i-1][j-1] + mod) % mod;
                }
            }
        }
    }
    cout << dp[n][m] << endl;
    cout << c[n][m] << endl;
}

这个方法还是太吃空间了,不如转换为一维数组。

void solve()
{
    string t;
    cin >> s >> t;
    n = s.size() - 1, m = t.size() - 1;
    vi dp(m + 1, 0);
    vi pre(m + 1, 0);
    vi c(m + 1, 0);//上一行
    vi p(m + 1, 1);//上一行

    for (int i = 1; i <= n; i++)
    {
        dp[0] = 0;//空串相同字符为0
        c[0] = 1;//空串反案为1
        for (int j = 1; j <= m; j++)
        {
            if (s[i - 1] == t[j - 1])
            {
                dp[j] = pre[j - 1] + 1;
                c[j] = p[j - 1];
                if (dp[j] == pre[j])
                    c[j] = (c[j] + p[j]) % mod;
                if (dp[j] == dp[j - 1])
                    c[j] = (c[j] + c[j - 1]) % mod;
            }
            else
            {
                dp[j] = max(pre[j], dp[j - 1]);
                c[j] = 0;
                if (dp[j] == pre[j])
                    c[j] = (c[j] + p[j]) % mod;
                if (dp[j] == dp[j - 1])
                    c[j] = (c[j] + c[j - 1]) % mod;
                if (dp[j] == pre[j - 1])
                    c[j] = (c[j] - p[j - 1] + mod) % mod;
            }
        }
        pre = dp;
        p = c;
    }
    cout << dp[m] << endl;
    cout << c[m] << endl;
}

4.求所有具体方案
直接把n,m,dp[n][m]传进去然后dfs就行了,如果dp[i][j-1]==dp[i-1][j],说明两边都要dfs,其他就是一边dfs,代码就不写了()。

posted @ 2026-03-19 17:48  Lambda_L  阅读(17)  评论(0)    收藏  举报