AcWing 272 最长公共上升子序列

题目描述:

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列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

分析:

本题要求a与b中公共子序列中的最长上升子序列,是LCS与LIS的综合问题。LCS问题中,f[i][j]表示a中前i个元素与b中前j个字符的最长公共子序列的长度,状态转移方程为a[i] == b[j]时,f[i][j] = f[i-1][j-1] + 1;a[i] != b[j]时,f[i][j] = max(f[i-1][j],f[i][j-1])。LIS问题中f[i]表示以a[i]为末尾的最长上升子序列长度。如果我们直接用f[i][j]表示a中前i个元素与b中前j个元素的最长公共上升子序列的长度。而求上升子序列时需要知道上升子序列是以哪个元素结尾的,否则无法扩展LIS的长度,所以不妨加个限制,即f[i][j]表示a中前i个元素与b中前j个元素的最长公共上升子序列的长度,且该子序列以b[j]结尾。

现在仿照LCS问题,对两个字符串的末字符进行比较,a[i] == b[j]时,f[i][j] = max(f[i - 1][k]) + 1,其中k < j,且b[k] < b[j]。之所以是f[i-1][k]而不是f[i-1][j-1]是因为这里的f加了以b[j]为末尾的限制,所以需要仿照LIS考察b[j]前的所有的元素。a[i] != b[j]时,由于最长公共上升子序列是以b[j]结尾的,所以a[i]肯定对该子序列没有贡献,于是f[i][j] = f[i-1][j]。这里注意在LIS问题中,边界条件是f[i] = 1,对本题而言,只要a的前i个元素与b的前j个元素有一个公共元素,则f[i][j]就应该不小于1,所以可以在a[i] == b[j]成立时,令f[i][j] = max(f[i][j],1),以此作为边界条件。代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3005;
int a[N],b[N],f[N][N];
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)   scanf("%d",&a[i]);
    for(int i = 1;i <= n;i++)   scanf("%d",&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]){
            	f[i][j] = max(f[i][j],1);
                for(int k = 1;k < j;k++){
                	if(b[j] > b[k])	f[i][j] = max(f[i][j],f[i - 1][k] + 1);
				}
            }
        }
    }
    int res = 0;
    for(int i = 1;i <= n;i++)   res = max(res,f[n][i]);
    cout<<res<<endl;
    return 0;
}

这里有个特别的现象就是f[i][j] = max(f[i][j],f[i - 1][k] + 1);,也可以写成f[i][j] = max(f[i][j],f[i][k] + 1);因为该式只有在a[i] = b[j] > b[k]时才被触发,所以在求f[i][k]时,执行完f[i][k] = f[i-1][k]后不会满足a[i] == b[k]的条件,原因很简单,a[i] = b[j] > b[k],所以满足a[i] == b[j] > b[k]条件时,f[i][k] = f[i-1][k]。如果把递推式写成f[i][j] = max(f[i][j],f[i][k] + 1),我们可以在前面遍历其他状态时把满足a[i]  > b[k]时前k个状态的f[i][k] + 1的最大值存储下来。从而将三层循环降至两层循环。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3005;
int a[N],b[N],f[N][N];
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)   scanf("%d",&a[i]);
    for(int i = 1;i <= n;i++)   scanf("%d",&b[i]);
    for(int i = 1;i <= n;i++){
        int t = 1;
        for(int j = 1;j <= n;j++){
            f[i][j] = f[i-1][j];
            if(a[i] == b[j]){
            	f[i][j] = max(f[i][j],t);
            }
            else if(b[j] < a[i])    t = max(t,f[i][j] + 1);
        }
    }
    int res = 0;
    for(int i = 1;i <= n;i++)   res = max(res,f[n][i]);
    cout<<res<<endl;
    return 0;
}

另外,由于求f[i][j]时只用到了上一层的状态,所以同样可以用滚动数组实现,理论上由于f[i][j]用到了上一层左边的状态,使用滚动数组在循环时需要倒着枚举以免结果被覆盖,但是由于满足a[i] = b[j] > b[k]时f[i][k] = f[i-1][k],因此覆盖后的值依旧是上一层的值,所以不用改变枚举顺序。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3005;
int a[N],b[N],f[N];
int main(){
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)   scanf("%d",&a[i]);
    for(int i = 1;i <= n;i++)   scanf("%d",&b[i]);
    for(int i = 1;i <= n;i++){
        int t = 1;
        for(int j = 1;j <= n;j++){
            if(a[i] == b[j]){
            	f[j] = max(f[j],t);
            }
            else if(b[j] < a[i])    t = max(t,f[j] + 1);
        }
    }
    int res = 0;
    for(int i = 1;i <= n;i++)   res = max(res,f[i]);
    cout<<res<<endl;
    return 0;
}
发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/104064303