【LCIS】最长公共上升子序列

本文深入讲解了最长公共上升子序列(LCIS)算法,通过详细的状态转移方程解释了如何求解两个序列的最长公共递增子序列。文章提供了完整的C++代码实现,并通过样例输入输出帮助理解算法流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1004 – 【TYVJ1071】LCIS最长公共上升子序列
Description
  熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们要研究最长公共上升子序列了。
  小沐沐说,对于两个串A,B,如果它们都包含一段位置不一定连续的数字,且数字是严格递增的,那么称这一段数字是两个串的公共上升子串,而所有的公共上升子串中最长的就是最长公共上升子串了。
  奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子串。不过,只要告诉奶牛它的长度就可以了。
Input
  第一行N,表示A,B的长度。
  第二行,串A。
  第三行,串B。
Output
  输出长度。
Sample Input
4
2 2 1 3
2 1 2 3
Sample Output
2
*状态转移方程:
①F[i][j] = F[i-1][j] (a[i] != b[j])
②F[i][j] = max(F[i-1][k]+1) (1 <= k <= j-1 && b[j] > b[k])
现在我们来说为什么会是这样的状态转移方程呢?
对于①,因为F[i][j]是以b[j]为结尾的LCIS,如果F[i][j]>0那么就说明a[1]…a[i]中必然有一个整数a[k]等于b[j],因为a[k]!=a[i],那么a[i]对F[i][j]没有贡献,于是我们不考虑它照样能得出F[i][j]的最优值。所以在a[i]!=b[j]的情况下必然有F[i][j]=F[i-1][j]。
对于②,前提是a[i] == b[j],我们需要去找一个最长的且能让b[j]接在其末尾的LCIS。之前最长的LCIS在哪呢?首先我们要去找的F数组的第一维必然是i-1。因为i已经拿去和b[j]配对去了,不能用了。并且也不能是i-2,因为i-1必然比i-2更优。第二维呢?那就需要枚举b[1]…b[j-1]了,
因为你不知道这里面哪个最长且哪个小于b[j]。这里还有一个问题,可不可能不配对呢?也就是在a[i]==b[j]的情况下,需不需要考虑F[i][j]=F[i-1][j]的决策呢?答案是不需要。因为如果b[j]不和a[i]配对,那就是和之前的a[1]…a[j-1]配对(假设F[i-1][j]>0,等于0不考虑),
这样必然没有和a[i]配对优越。为什么必然呢?
因为b[j]和a[i]配对之后的转移是max(F[i-1][k])+1,而和之前的i配对则是max(F[i-1][k])+1。

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=5005;
int a[maxn],b[maxn],f[maxn],n;
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
	return s*w;
}
int main()
{
	int i,j,maxx=0;
	n=read();
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	//a[i]=read();
	for(i=1;i<=n;i++)scanf("%d",&b[i]);
	//b[i]=read();
	for(i=1;i<=n;i++)
	{
		maxx=0;
		for(j=1;j<=n;j++)
		{
			if(b[j]<a[i]&&maxx<f[j])maxx=f[j];
			if(b[j]==a[i])f[j]=maxx+1;
		}
	}
	maxx=0;
	for(i=1;i<=n;i++)if(maxx<f[i])maxx=f[i];
	printf("%d",maxx);
	return 0;
}
### 最长公共上升子序列问题的解决方案 最长公共上升子序列(Longest Common Increasing Subsequence, LCIS)问题是一个结合了最长公共子序列(LCS)和最长上升子序列(LIS)的问题。它要求找到两个序列的最长公共子序列,同时该子序列必须是严格递增的。 #### 动态规划解法 动态规划是一种高效的方法来解决此类问题。定义状态 `dp[i][j]` 表示以第一个序列的前 `i` 个元素和第二个序列的前 `j` 个元素为考虑范围时,最长公共上升子序列的长度[^1]。 ##### 状态转移方程 对于两个序列 `A` 和 `B`,其长度分别为 `n` 和 `m`,状态转移方程如下: - 如果 `A[i] == B[j]` 并且存在一个比 `A[i]` 小的值使得当前子序列递增,则更新 `dp[i][j]`。 - 否则,`dp[i][j]` 取决于之前的状态值。 具体实现中,可以引入一个辅助数组 `prev` 来记录每个位置的最优前驱,以便后续回溯出具体的子序列[^2]。 ```python def longest_common_increasing_subsequence(A, B): n, m = len(A), len(B) dp = [[0] * (m + 1) for _ in range(n + 1)] prev = [[-1] * (m + 1) for _ in range(n + 1)] for i in range(1, n + 1): for j in range(1, m + 1): if A[i - 1] == B[j - 1]: # 找到一个可能的递增点 max_len = 0 for k in range(j): if B[k - 1] < B[j - 1] and dp[i - 1][k] > max_len: max_len = dp[i - 1][k] dp[i][j] = max_len + 1 else: dp[i][j] = dp[i][j - 1] # 回溯找出具体子序列 result = [] max_len = 0 pos = 0 for j in range(1, m + 1): if dp[n][j] > max_len: max_len = dp[n][j] pos = j while pos != -1: result.append(B[pos - 1]) next_pos = prev[n][pos] pos = next_pos return result[::-1], max_len ``` #### 时间复杂度分析 上述算法的时间复杂度为 \(O(n \times m^2)\),其中 \(n\) 和 \(m\) 分别是两个序列的长度。这是因为内层循环需要遍历所有可能的前驱节点以确保递增性[^3]。 #### 示例代码 以下是一个简单的例子,展示如何使用上述函数: ```python A = [3, 4, 9, 1] B = [5, 3, 8, 9, 10, 2, 1] result, length = longest_common_increasing_subsequence(A, B) print("最长公共上升子序列:", result) print("长度:", length) ``` #### 输出结果 ``` 最长公共上升子序列: [3, 9] 长度: 2 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值