最长公共上升子序列

算法
(DP,线性DP,前缀和) O(n2)O(n2)
这道题目是AcWing 895. 最长上升子序列和AcWing 897. 最长公共子序列的结合版,在状态表示和状态计算上都是融合了这两道题目的方法。

状态表示:

f[i][j]代表所有a[1 ~ i]和b[1 ~ j]中以b[j]结尾的公共上升子序列的集合;
f[i][j]的值等于该集合的子序列中长度的最大值;
状态计算(对应集合划分):

首先依据公共子序列中是否包含 a [ i ] a[i] a[i],将 f [ i ] [ j ] f[i][j] f[i][j]所代表的集合划分成两个不重不漏的子集:

不包含 a [ i ] 的 子 集 , 最 大 值 是 f [ i − 1 ] [ j ] a[i]的子集,最大值是f[i - 1][j] a[i]f[i1][j]
包含a[i]的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在b[]中是哪个数:
子序列只包含b[j]一个数,长度是1;
子序列的倒数第二个数是 b [ 1 ] 的 集 合 , 最 大 长 度 是 f [ i − 1 ] [ 1 ] + 1 b[1]的集合,最大长度是f[i - 1][1] + 1 b[1]f[i1][1]+1

子序列的倒数第二个数是 b [ j − 1 ] 的 集 合 , 最 大 长 度 是 f [ i − 1 ] [ j − 1 ] + 1 b[j - 1]的集合,最大长度是f[i - 1][j - 1] + 1 b[j1]f[i1][j1]+1
如果直接按上述思路实现,需要三重循环

#include<bits/stdc++.h>
#define I inline
#define RI register int 
#define N 5500
using namespace std;
I int read()
{
	RI res=0,f=1;char ch=getchar();
	while(!isdigit(ch))if(ch=='-')f=-f,ch=getchar();else ch=getchar();
	while(isdigit(ch))res=(res<<1)+(res<<3)+(ch&15),ch=getchar();
	return res*f;
}
int f[N][N],n,a[N],b[N],num[N][N];
int main()
{
	n=read();for(RI i=1;i<=n;i++)a[i]=read();
	for(RI i=1;i<=n;i++)b[i]=read();
	for(RI i=1;i<=n;i++)
	{
		for(RI j=1;j<=n;j++)
		{
	      f[i][j]=f[i-1][j];
	      if(a[i]==b[j])
		  {
	      	int Max=1;for(RI k=1;k<j;k++)
			if(a[i]>b[k])Max=max(Max,f[i-1][k]+1);
			f[i][j]=max(f[i][j],Max);
		  }
		}
	}
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    printf("%d\n", res);
	return 0;
}

然后我们发现每次循环求得的maxv是满足 a [ i ] > b [ k ] 的 f [ i − 1 ] [ k ] + 1 a[i] > b[k]的f[i - 1][k] + 1 a[i]>b[k]f[i1][k]+1的前缀最大值。
因此可以直接将maxv提到第一层循环外面,减少重复计算,此时只剩下两重循环。

最终答案枚举子序列结尾取最大值即可。

时间复杂度
代码中一共两重循环,因此时间复杂度是 O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>
#define I inline
#define RI register int 
#define N 3500
using namespace std;
I int read()
{
	RI res=0,f=1;char ch=getchar();
	while(!isdigit(ch))if(ch=='-')f=-f,ch=getchar();else ch=getchar();
	while(isdigit(ch))res=(res<<1)+(res<<3)+(ch&15),ch=getchar();
	return res*f;
}
int f[N][N],n,a[N],b[N];
int main()
{
	n=read();for(RI i=1;i<=n;i++)a[i]=read();
	for(RI i=1;i<=n;i++)b[i]=read();int Max=1;
	for(RI i=1;i<=n;i++)
	{
		Max=1;
		for(RI j=1;j<=n;j++)
		{
	      f[i][j]=f[i-1][j];
	      if(a[i]==b[j])f[i][j]=max(f[i][j],Max);
		  if(a[i]>b[j])Max=max(Max,f[i-1][j]+1);
		}
	}
    int res=0;for(int i=1;i<=n;i++)res=max(res,f[n][i]);
    printf("%d\n", res);
	return 0;
}

<think>嗯,用户问的是C++最长公共上升子序列。首先,我得确认他们需要的是这个问题的解决方法,还是想了解概念。最长公共上升子序列(LCIS)结合了最长公共子序列(LCS)和最长上升子序列(LIS)的特点,所以可能需要先解释一下这个概念。 然后,我得回忆一下LCIS的动态规划解法。通常,这类问题会用一个二维数组dp[i][j]来表示处理到第一个数组的第i个元素和第二个数组的第j个元素时的状态。但可能需要优化空间复杂度,或者有没有更高效的方法? 比如,可能有一个优化的方法,遍历第二个数组的同时维护一些状态,比如当前的最大长度,或者某种条件下的最大值。这样可以将时间复杂度降到O(n^2)而不是更高的复杂度。比如,原版的LCS是O(n^2),而LIS如果使用优化方法可以到O(n log n),但这里两者结合可能需要不同的处理。 举个例,假设有两个数组a和b。我们需要找到既是两者的公共子序列,又是严格递增的。这个时候,动态规划的状态设计可能需要考虑到当前元素的值,以确保递增的条件。 可能的状态转移方程是怎样的?比如,当a[i] == b[j]的时候,我们需要找到一个k,使得b[j] > 当前某个值,然后更新dp[j]的值。或者,可以维护一个数组,其中dp[j]表示以b[j]结尾的LCIS的长度。这样的话,每次处理a中的元素时,遍历b数组,找到符合条件的j的位置,并更新dp值。 比如,当处理到a的第i个元素时,对于每个b中的元素j,如果a[i] == b[j],那么我们需要找到在b中比b[j]小的所有位置k,然后dp[j] = max(dp[j], dp[k]+1)。或者,可以维护一个变量来记录当前的最大值,从而避免重复遍历。 或者,另一种方法是,对于每一个元素在数组a中,遍历数组b中的元素,同时记录当前的最大长度。比如,对于数组b中的每个元素,如果它等于当前a中的元素,并且之前的元素满足递增条件,则更新对应的dp值。 这个时候可能需要一个双重循环,外层遍历数组a的元素,内层遍历数组b的元素,并维护一个变量来记录在当前位置之前的最大可行长度。这样时间复杂度是O(nm),其中n和m是两个数组的长度。 比如,初始化一个数组dp,长度为m,初始为0。然后对于每个a中的元素a[i],维护一个变量current_max,初始为0。然后遍历b数组中的每个元素b[j],如果a[i] > b[j],则current_max = max(current_max, dp[j]),因为这样当后面遇到a[i]等于某个b[j]时,可以继承这个current_max加1。如果a[i]等于b[j],则dp[j] = current_max + 1,这样保证递增的条件。 这样处理的话,当a[i]等于b[j]时,dp[j]的值取决于之前所有比b[j]小的元素的dp值中的最大值加1,这样确保了上升的特性。同时,因为a和b的公共元素必须按顺序出现,所以这样处理可以保证公共子序列的顺序。 现在需要将这个过程用C++代码实现的话,可能需要两个数组,或者优化空间使用一个一维数组。例如,用一个一维数组dp,其中dp[j]表示以b[j]结尾的最长公共上升子序列的长度。 具体的代码结构可能是这样的: 初始化dp数组为0。 遍历数组a中的每个元素x: 初始化current_max为0。 遍历数组b中的每个元素y及其索引j: 如果y < x,则current_max = max(current_max, dp[j]) 否则如果y == x,则dp[j] = max(dp[j], current_max + 1) 最后,dp数组中的最大值就是LCIS的长度。 这样处理的话,时间复杂度是O(nm),空间复杂度是O(m),如果数组b的长度是m的话。 然后需要考虑边界情况,比如两个数组中有重复元素,或者其中一个数组为空的情况。另外,是否需要严格递增还是非严格递增?题目中的“上升”通常指严格递增,所以在判断条件时要注意。 例如,如果a数组是[1,3,2,4],b数组是[1,2,3,4],那么LCIS应该是[1,2,4]或者[1,3,4],长度3。 测试这样的例是否能被正确处理。 现在,在代码实现时,需要注意循环的顺序。外层是a的每个元素,内层是遍历b数组。每次处理a中的一个元素时,先遍历b数组,记录比当前a元素小的位置的最大dp值,然后在遇到相等的元素时更新对应的dp[j]。 这样,每次处理a的元素时,可以正确累积前面的信息。例如,当a[i]等于b[j]时,此时current_max保存的是所有在b中位置j之前且元素值小于a[i]的位置的最大dp值,因此将dp[j]更新为current_max+1是正确的。 最后,遍历dp数组找到最大值即可。 这样的算法应该能正确处理问题。那现在需要给出一个C++的示例代码。例如: #include <iostream> #include <vector> #include <algorithm> using namespace std; int LCIS(vector<int>& a, vector<int>& b) { int m = b.size(); vector<int> dp(m, 0); for (int x : a) { int current_max = 0; for (int j = 0; j < m; ++j) { if (b[j] < x) { current_max = max(current_max, dp[j]); } else if (b[j] == x) { dp[j] = max(dp[j], current_max + 1); } } } return *max_element(dp.begin(), dp.end()); } int main() { vector<int> a = {1, 3, 2, 4}; vector<int> b = {1, 2, 3, 4}; cout << LCIS(a, b) << endl; // 输出3 return 0; } 这样应该可以得到正确的结果。当然,需要处理其他情况,比如数组为空的情况。但在这个示例中,假设输入的数组都是非空的。 总结来说,回答用户的问题时,应该先解释LCIS的定义,然后介绍动态规划的解法,说明状态转移的思路,再给出代码示例,并解释其原理。同时需要注意时间复杂度和空间复杂度,以及可能的优化点。最长</think>公共上升子序列(Longest Common Increasing Subsequence,LCIS)是结合了最长公共子序列(LCS)和最长上升子序列(LIS)的问题,要求找到两个序列中既是公共子序列又是严格递增的最长子序列。以下是详细解释和C++实现: --- ### **问题描述** 给定两个整数序列 `a` 和 `b`,找到它们的最长公共上升子序列的长度。 --- ### **动态规划解法思路** 1. **状态定义**: 使用一维数组 `dp[j]`,表示以 `b[j]` 结尾的最长公共上升子序列的长度。 2. **状态转移**: - 遍历 `a` 的每个元素 `x`,同时在遍历 `b` 时维护一个变量 `current_max`。 - 当 `b[j] < x` 时,更新 `current_max` 为当前已知的最大可能长度。 - 当 `b[j] == x` 时,将 `dp[j]` 更新为 `current_max + 1`,确保子序列严格递增。 3. **时间复杂度**:O(n*m),其中 `n` 和 `m` 是两序列的长度。 --- ### **C++代码实现** ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int LCIS(const vector<int>& a, const vector<int>& b) { int m = b.size(); vector<int> dp(m, 0); // dp[j] 表示以 b[j] 结尾的LCIS长度 for (int x : a) { int current_max = 0; // 维护当前x之前的最大可行长度 for (int j = 0; j < m; ++j) { if (b[j] < x) { current_max = max(current_max, dp[j]); // 更新current_max } else if (b[j] == x) { dp[j] = max(dp[j], current_max + 1); // 更新以b[j]结尾的LCIS } } } return *max_element(dp.begin(), dp.end()); // 返回dp数组中的最大值 } int main() { vector<int> a = {1, 3, 2, 4}; vector<int> b = {1, 2, 3, 4}; cout << LCIS(a, b) << endl; // 输出3(序列为1,2,4 或 1,3,4) return 0; } ``` --- ### **关键点解释** - **双循环遍历**:外层遍历 `a` 的元素,内层遍历 `b`,确保公共子序列的顺序。 - **current_max 的作用**:记录当前 `a` 元素之前所有小于 `b[j]` 的元素能构成的最大长度,保证严格递增。 - **空间优化**:使用一维数组而非二维数组,空间复杂度降至 O(m)。 --- ### **示例分析** 对于输入 `a = [1,3,2,4]` 和 `b = [1,2,3,4]`: - 公共上升子序列包括 `[1,2,4]` 和 `[1,3,4]`,长度均为3。 - 代码通过动态规划正确计算得到结果。 该算法高效且直观,适用于中等规模的输入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值