子序列(省略已知值法与挪位得解法)

本文探讨了一个特定的算法问题——如何将一个偶数长度的序列划分为两个递增子序列。通过动态规划的方法解决了这一问题,并给出了具体的实现代码。适用于算法竞赛及数据结构学习。

Problem 1 子序列(subsequence.pas/c/cpp)

【题目描述】

给定一个长度为N(N为偶数)的序列,问能否将其划分为两个长度为N/2的严格递增子序列,

 

【输入格式】

若干行,每行表示一组数据。对于每组数据,首先输入一个整数N,表示序列的长度。之后N个整数表示这个序列。

 

【输出格式】

同输入行数。对于每组数据,如果存在一种划分,则输出“Yes!”,否则输出“No!“。

 

【样例输入】

6 3 1 4 5 8 7

6 3 2 1 6 5 4

【样例输出】

Yes!

No!

【数据范围】

共三组数据,每组数据行数<=50,0 <= 输入的所有数 <= 10^9

第一组(30%):N <= 20

第二组(30%):N <= 100

第三组(40%):N <= 2000


一般青年Dp方案:F[i][j][k][l] 表示前i+j位分为一个长度为ij结尾,一个长度为kl结尾的序列 是否可行(0,1)

省略已知值:观察发现jl中至少有一个为a[i+j]

故可省略其中一位

n=2000必跪

文艺青年Dp方案:

挪位得解:把f[i][j][k]中的k挪出来 

原因:显然i和j不变时,我们希望k越小越好

所以记录min(k),并记录无解情况

O(n^2)


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<functional>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXN (2000+10)
#define INF (2139062143)
int n,a[MAXN],f[MAXN][MAXN];
int main()
{
	freopen("subsequence.in","r",stdin);
	freopen("subsequence.out","w",stdout);
	while (scanf("%d",&n)!=EOF)
	{
		memset(f,127,sizeof(f));
		for (int i=1;i<=n;i++) scanf("%d",&a[i]);
		f[1][1]=-1;
		for (int i=1;i<n;i++)
		{
			for (int j=1;j<=i;j++)
			{
				if (f[i][j]!=INF)
				{
					if (a[i]<a[i+1]) f[i+1][j+1]=min(f[i+1][j+1],f[i][j]);
					if (f[i][j]<a[i+1])
						f[i+1][i-j+1]=min(f[i+1][i-j+1],a[i]);
				}
			}	
		}
		/*
		for (int i=0;i<=n;i++)
		{
			for (int j=0;j<=n;j++)
				printf("%d ",f[i][j]);
			printf("\n");
		}
		*/
		if (f[n][n>>1]!=INF) printf("Yes!\n");
		else printf("No!\n");		
	}	
	return 0;
}


### 使用分治求解最长上升子序列(LIS)问题 #### 分治的核心思想 分治通过将复杂问题分解成更小的子问题来逐步解决问题。对于最长上升子序列(LIS),可以利用分治的思想,将整个序列划分为两个部分,并分别计算每部分的 LIS 长度。最终的结果可以通过合并这两个部分得出。 在分治过程中,确定分界点是一个关键步骤[^2]。通常情况下,可以选择序列的中间置作为分界点,从而将原始序列分割为左半部分和右半部分。 #### 算实现逻辑 以下是基于分治的 LIS 实现的主要逻辑: 1. **递归划分**:将输入序列不断二分,直到每个子序列仅包含单个元素为止。 2. **局部最优解**:针对每一个子序列,独立计算其内部可能存在的最长上升子序列。 3. **合并阶段**:当左右两部分都已找到各自的 LIS 后,尝试构建跨越这两部分的新候选 LIS 序列。 需要注意的是,在某些变种题目中可能会额外规定必须经过某固定索引处的数据项,则此时需进一步调整策略以满足条件限制。 下面提供一段 Python 的伪代码用于演示该方的具体操作流程: ```python def lis_divide_conquer(sequence): n = len(sequence) # 边界情况处理 if n == 0: return [] elif n == 1: return sequence[:] mid = n // 2 # 左侧递归调用 left_lis = lis_divide_conquer(sequence[:mid]) # 右侧递归调用 right_lis = lis_divide_conquer(sequence[mid:]) # 寻找左侧最大小于右侧最小置组合形成新的lis路径 combined_lis = merge_two_subsequences(left_lis, right_lis) return max([left_lis, right_lis, combined_lis], key=len) # 假设存在这样一个函数能够完成跨区间连接工作 def merge_two_subsequences(seq_left, seq_right): pass # 这里省略实际细节实现 ``` 上述代码片段展示了基本框架结构,其中 `merge_two_subsequences` 函数负责考虑如何有效率地把来自不同分区内的潜在升序链条拼接起来构成全局范围内的最佳解答方案之一。 尽管如此,得注意的一点在于采用纯正意义上的“分而治之”方式去单独应对标准版无附加约束条件下单纯追求长度最大的经典型 LIS 查询任务未必是最高效的选择;相比之下,运用动态规划技术往往能带来更低时间复杂度的表现效果[^4]。 然而,当我们面临一些特殊的场景需求——例如强制要求所选中的数列必须覆盖住给定数据集中间部的那个数节点时,那么借助于分治思维模式则显得尤为必要且合理适用了。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值