Bootstrap

最长公共子序列 + 最长上升子序列 + 最长公共上升子序列

最长公共子序列

题目描述

给定两个长度分别为N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串长度最长是多少。
输入格式
第一行包含两个整数N和M。
第二行包含一个长度为N的字符串,表示字符串A。
第三行包含一个长度为M的字符串,表示字符串B。
字符串均由小写字母构成。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N,M≤1000
输入样例:
4 5
acbd
abedc
输出样例:
3

题解

在这里插入图片描述

状态计算考虑最后一个元素,分别考虑a[i], b[j] 是否在集合中,这里的集合是指既包含在a[1~i]中,又包含在b[1-j]中。0表示在,1表示不在。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
const int N = 1010;

int f[N][N];
char a[N], b[N];

int main()
{
    int n, m;
    cin >> n >> m;
    
    cin >> a + 1 >> b + 1; //避免下标越界,起始位置从1开始
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(a[i] != b[j])
            {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            }
            else
            {
                f[i][j] = f[i - 1][j - 1] + 1;
            }
        }
    }
    
    cout << f[n][m] << endl;
    
    return 0;
}

最长上升子序列

题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

题解

在这里插入图片描述

状态计算考虑最后一个元素,判断在a[1~i]中是否有一个元素a[k]( 0 =< k <= i)小于a[i]
小于的话就可以转移。

解法一

 int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1, 1);
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(nums[j] < nums[i])
                {
                    f[i] = max(f[i], f[j] + 1);
                }
            }
        }


        int res = 0;
        for(int i = 0; i < n; i++) res = max(f[i], res);

        return res;
    }

解法二

依然是着眼于一个上升子序列的结尾的元素;
如果已经得到的上升子序列的结尾的数越小,遍历的时候后面接上一个数,就会有更大的可能性构成一个更长的上升子序列;
既然结尾越小越好,我们可以记录在长度固定的情况下,结尾最小的那个元素的数值,这样定义也是为了方便得到「状态转移方程」。
为了与之前的状态区分,这里将状态数组命名为 tail。
定义新状态(特别重要):tail[i] 表示长度为 i + 1 的所有上升子序列的结尾的最小值。

int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        
        if(n < 2) return n;

        vector<int> tail; //长度为i的上升子序列末尾元素

        tail.push_back(nums[0]);
        int end = 0;
        for(int i = 1; i < n; i++)
        {
            if(nums[i] > tail[end])
            {
                tail.push_back(nums[i]);
                end++;
            }
            else
            {
                int l = 0, r = end;
                while(l < r)
                {
                    int mid = l + r >> 1;
                    if(tail[mid] < nums[i]) l = mid + 1;
                    else r = mid;
                }

                tail[l] = nums[i];
            }
        }

        return end + 1;
    }

最长公共上升子序列

题目描述

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。
小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列A和B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。
奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。
不过,只要告诉奶牛它的长度就可以了。
数列A和B的长度均不超过3000。
输入格式
第一行包含一个整数N,表示数列A,B的长度。
第二行包含N个整数,表示数列A。
第三行包含N个整数,表示数列B。
输出格式
输出一个整数,表示最长公共上升子序列的长度。
数据范围
1≤N≤3000,序列中的数字均不超过231−1
输入样例:
4
2 2 1 3
2 1 2 3
输出样例:
2

题解

1.状态表示:
f[i][j]代表所有a[1 ~ i]和b[1 ~ j]中以b[j]结尾的公共上升子序列的集合;
f[i][j]的值等于该集合的子序列中长度的最大值;
2.状态计算(对应集合划分):
首先依据公共子序列中是否包含a[i],将f[i][j]所代表的集合划分成两个不重不漏的子集:
不包含a[i]的子集,最大值是f[i - 1][j];
包含a[i]的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在b[]中是哪个数:
子序列只包含b[j]一个数,长度是1;
子序列的倒数第二个数是b[1]的集合,最大长度是f[i - 1][1] + 1;

子序列的倒数第二个数是b[j - 1]的集合,最大长度是f[i - 1][j - 1] + 1;

算法优化:
优化第二层循环。因为a[i] == b[j], 第三层循环的作用是求小于b[j]的方案数,也即是求小于a[i]的方案数, 求完一个j后,剩下的j继续往后走变成j+1,但第一层循环i保持不变。即if(b[j + 1] = a[i]), 又会执行第三层循环,这时就会重复求解原来求过的数。

#include "iostream"

using namespace std;

const int N = 3010;

int f[N][N];

int a[N], b[N];

int main(){
    
    int n;
    cin>>n;
    for(int i = 1; i <= n; i++){
        cin>>a[i];
    }
    
    for(int i = 1; i <= n; i++){
        cin>>b[i];
    }
    
    //朴素算法
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            f[i][j] = f[i-1][j];
            if(a[i] == b[j]){
                int mmax = 1;
                for(int k = 1; k < j; k++){
                    if(b[k]<b[j]){
                        mmax = max(mmax, f[i-1][k]+1);
                    }
                }
                
                f[i][j] = max(f[i][j], mmax);
            }
        }
    }
    
    //优化后的算法
    /* for(int i = 1; i <= n; i++){
        int mmax = 1;
        for(int j = 1; j <= n; j++){
            f[i][j] = f[i-1][j];
            if(b[j]<a[i]) mmax = max(mmax, f[i-1][j]+1);
            if(a[i] == b[j]) f[i][j] = max(f[i][j], mmax);
            
            }
        }
    */
    
    int res = 0;
    
    for(int i = 1; i <= n; i++) res = max(res, f[n][i]);
    
    printf("%d\n", res);
     
    
    return 0;
}

链接:https://www.acwing.com/solution/content/4955/

;