最长上升子序列、最长公共子序列、最长公共上升子序列(LIS、LCS、LCIS)

最长上升子序列LIS

题目链接:AcWing895. 最长上升子序列
在这里插入图片描述
这里只说明 O ( n 2 ) O(n^2) O(n2)的解法, O ( n l o g n ) O(nlogn) O(nlogn)解法之前的博客有介绍
O ( n 2 ) O(n^2) O(n2)的解法较为容易理解

状态表示:
这里状态用一维表示,dp[i]表示以b[i]作为结尾的最长子序列的长度

状态转移:
遍历b[i]前的所有元素,找出最长的以b[i]结尾的最长子序列长度
for(int j=0;j<i;j++)
	if(b[j]<b[i]) dp[i]=max(dp[i],dp[j]+1)

代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int b[N];
int dp[N];
int main(){
    int n,res=0;
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    b[0]=-0x3f3f3f3f;
    for(int i=1;i<=n;i++){
        for(int j=0;j<i;j++)
            if(b[i]>b[j]) dp[i]=max(dp[i],dp[j]+1);
        res=max(res,dp[i]);
    }
    cout<<res;
    return 0;
}

最长公共子序列LCS

题目链接:AcWing897. 最长公共子序列
在这里插入图片描述

状态表示:
状态用二维表示,dp[i][j]表示A[1~i],B[1~j]的最长公共子序列

状态转移:
if(A[i]==B[j]) dp[i][j]=dp[i-1][j-1]+1;//最长公共子序列增长一位
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);//从两种情况中选择长的一种传递

代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int dp[N][N];//dp[i][j]表示A[1~i]与B[1~j]最长公共子序列
char A[N],B[N];
int main(){
    int n,m;
    cin>>n>>m;
    cin>>A+1>>B+1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(A[i]==B[j]) dp[i][j]=dp[i-1][j-1]+1;
            else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
    cout<<dp[n][m];
    return 0;
}

最长公共上升子序列LCIS

AcWing272. 最长公共上升子序列
在这里插入图片描述
这里我们考虑将上面的LIS和LCS结合

状态表示:
状态用二维来表示
dp[i][j]表示A[1~i]与B[1~j]且以B[j],结尾的最长公共上升子序列长度

状态转移:
 if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
 	else {
		int t=0;
		for(int k=1;k<j;k++)
			if(B[k]<B[j]) t=max(t,dp[i][k]);
	dp[i][j]=t+1;
}
当A[i]!=B[j]时,状态从dp[i-1][j]转移来
当A[i]==B[j]时,我们要考虑将B[j]前面求出的最长公共上升子序列中的哪个子序列之后,所以需要遍历前面所有的子序列(A[i]与B[1~k],k<j匹配的子序列)

代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=3010;
int A[N],B[N];
int dp[N][N];//dp[i][j]表示A[1~i]与B[1~j]且以B[j],结尾的最长公共上升子序列

int main(){
    int n;
    cin>>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++)
            if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
            else {
                int t=0;
                for(int k=1;k<j;k++)
                    if(B[k]<B[j]) t=max(t,dp[i][k]);
                dp[i][j]=t+1;
            }
    }
    int res=0;
    for(int i=1;i<=n;i++)
        res=max(res,dp[n][i]);
    cout<<res;
    return 0;
}

我们可以发现这里的时间复杂度为 O ( n 3 ) O(n^3) O(n3),能处理的数据范围很小
我们可以对上面的代码进行优化,
其中

if(B[k]<B[j]) t=max(t,dp[i][k]);
此处的B[j]==A[i],所以可以写成
if(B[k]<A[i]) t=max(t,dp[i][k]);
我们要找到最大的B[k],可以提前将max(B[k])求出来

将下面代码优化

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
            else {
                int t=0;
                for(int k=1;k<j;k++)
                    if(B[k]<B[j]) t=max(t,dp[i][k]);
                dp[i][j]=t+1;
            }
    }

可以在遍历B[1~j]中求出B[k]<A[i]时的最大的dp[i][k]

    for(int i=1;i<=n;i++){
        int t=0;
        for(int j=1;j<=n;j++){
            if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
            else dp[i][j]=t+1;
            if(A[i]>B[j]) t=max(t,dp[i][j]);
        }
    }

代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=3010;
int A[N],B[N];
int dp[N][N];//dp[i][j]表示A[1~i]与B[1~j]且以B[j],结尾的最长公共上升子序列

int main(){
    int n;
    cin>>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=0;
        for(int j=1;j<=n;j++){
            if(A[i]!=B[j]) dp[i][j]=dp[i-1][j];
            else dp[i][j]=t+1;
            if(A[i]>B[j]) t=max(t,dp[i][j]);
        }
    }
    int res=0;
    for(int i=1;i<=n;i++)
        res=max(res,dp[n][i]);
    cout<<res;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chp的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值