最长公共上升子序列(DP)

问题描述:在动态规划中,最长公共子序列(LCS)与最长上升子序列(LIS)是基础的线性DP。现在要求求出两个不等长序列的最长公共上升子序列,故名思意,定义类似前两个问题。

解题思路:如果已经会求最大公共子序列,那么自然就可以想到,只需在最长公共子序列上找一遍最长上升子序列即可。然而具体的状态转移方程,只要在两个元素判等后进行进一步操作即可,稍微注意一下状态的始末,即k为何从0开始而不是1。该方法为O(n^3)。

#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 1e3+10;
int a[maxn],b[maxn];
int F[maxn][maxn];
void solve(int n,int m){
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= m;j++){
			if(a[i] == b[j]){
				for(int k = 0;k < j;k++)
				if(b[k] < a[i])
				F[i][j] = max(F[i][j],F[i-1][k]+1);	
			} 
			else F[i][j] = F[i-1][j];
		}
	}
	int mx = 0;
	for(int i = 1;i <= m;i++) mx = max(mx,F[n][i]);
	printf("%d\n",mx);
}
int main(){
	freopen("123.in","r",stdin); 
	int t;
	scanf("%d",&t);
	while(t--){
		int n,m;
		scanf("%d",&n);
		for(int i = 1;i <= n;i++)	scanf("%d",a+i);
		scanf("%d",&m);
		for(int i = 1;i <= m;i++)	scanf("%d",b+i);
		solve(n,m);
	}
	return 0;
}

优化:观察上述代码中主体部分(solve函数中三层循环),可以发现我们用了一层循环来找寻上一层最大值(即F[ i-1 ][ k ])。我们的策略是当a[i] 和 b[j] 相等,再找寻其之前的 、小于a[i]的最长公共上升子序列并+1长度,实际上我们需要知道的只有之前的最长公共上升子序列的长度,这样一来我们可以用一个变量val来存放 F[ i-1 ][ k ] 中的最大值。当a[i] 和 b[j] 相等时,F[ i ][ j ] 就等于val,然后只需要再用当前的 b[ j ] 来更新 val(如果b[ j ] < a[ i ],说明以第 i 个元素为末尾的序列a以及以 j 为末尾的序列b可能有更大的值,故用来更新val),由于我们总是从第一个元素向后递推,故既不会重复也不会遗漏(这在动态规划是很重要的一点)。

#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 1e3+10;
int a[maxn],b[maxn];
int F[maxn][maxn];
void solve(int n,int m){
	for(int i = 1;i <= n;i++){
		int val = 0;
		if(b[0] < a[i]) val = F[i-1][0];
		for(int j = 1;j <= m;j++){
			if(a[i] == b[j]) F[i][j] = val+1;
			else F[i][j] = F[i-1][j];
			if(b[j] < a[i]) val = max(val,F[i-1][j]);
		}
	}
	int mx = 0;
	for(int i = 1;i <= m;i++) mx = max(mx,F[n][i]);
	printf("%d\n",mx);
}
int main(){
	freopen("123.in","r",stdin); 
	int t;
	scanf("%d",&t);
	while(t--){
		int n,m;
		scanf("%d",&n);
		for(int i = 1;i <= n;i++)	scanf("%d",a+i);
		scanf("%d",&m);
		for(int i = 1;i <= m;i++)	scanf("%d",b+i);
		solve(n,m);
	}
	return 0;
}

笔记整理自:《算法竞赛进阶指南》李煜东 P242。个人心得整理,方便复习。由于本人能力有限,表达多有不尽意之处,但作为个人笔记已达到效果,见谅。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

迷亭1213

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

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

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

打赏作者

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

抵扣说明:

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

余额充值