2022第十三届蓝桥杯省赛C++A组:最长不下降子序列(二维动态规划解法)

文章详细阐述了如何运用二维动态规划解决一个编程问题,涉及计算最长可被修改为不下降序列的子序列长度,通过实例和代码展示了解题步骤。

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

题目描述

笔者认为该题目有些问题,序列应该是可以不连续的,不过这个题目是按连续子序列来的,那就按要求做了

解题思路

对于该题考虑二维动态规划,dp[i]用来表示以nums[i]为结尾的序列,其最长不下降的序列长度,dp[i][0]代表没有修改过的序列,dp[i][1]代表已修改过的序列,初始状态dp[0][0]=dp[0][1]=1

用一个序列进行举例 5 6 26 23 22 21 27 10 k=3该序列最长不下降子序列长度为7

对该数组用两次循环进行遍历

对于nums[i]<=nums[j],动态转移方程为

dp[j][0] = max(dp[i][0] + 1, dp[j][0]),未修改的最长子序列长度+1
dp[j][1] = max(dp[i][1] + 1, dp[j][1]),已修改的最长子序列长度+1

即在原本dp[i]的最长子序列上加1,本例中5,6,26即为以26结尾的最长子序列,dp[2][0]=dp[2][1]=3

对于nums[j]>nums[i],需要执行三步,

第一步,dp[j][1] = max(dp[i][0] + 1, dp[j][1]);即未修改的状态->已修改的状态,例如5 6 26 23 直接修改为5 6 26 26,故以nums[3]=23结尾的已修改的最长子序列长度为4

第二步,k=3,从nums[3]开始的3个数均可改为nums[2]=26,故dp[i + t][1] = max(dp[i][0] + t, dp[i + t][1]),其中1<=t<=k,即以23,22,21结尾的最长子序列均可在前面基础上+1,即改成5 6 24 24 24 24,此时最长子序列长度为6

第三步,从21改为26继续往后遍历,后面27比26大,故以27结尾的子序列长度再+1,为7,往后碰到10,比26小,终止遍历

最终dp[i][1]中含有最佳状态,在本例中最佳状态以27结尾即dp[6][1]=7

#include<bits/stdc++.h>
using namespace std;
int N, k;
vector<int> nums;
int main() {
	cin >> N >> k;
	vector<int> t(2, 1);
	vector<vector<int>> dp(N, t);
	while (N--) {
		int temp;
		cin >> temp;
		nums.push_back(temp);
	}
	for (int i = 0; i < nums.size(); i++) {
		int j = i + 1;
		for (int j = i + 1; j < nums.size(); j++) {
			if (nums[i] <= nums[j])
			{
				dp[j][0] = max(dp[i][0] + 1, dp[j][0]);
				dp[j][1] = max(dp[i][1] + 1, dp[j][1]);
			}
			else {
				dp[j][1] = max(dp[i][0] + 1, dp[j][1]);
				for (int t = 1; t <= k && i + t < nums.size(); t++) {
					dp[i + t][1] = max(dp[i][0] + t, dp[i + t][1]);
				}
				for (int t = i + k + 1; t < nums.size(); t++) {
					if (nums[i] <= nums[t])
						dp[t][1] = max(dp[i][0] + t - i, dp[t][1]);
					else break;
				}
				break;
			}
		}
		/*cout << i << endl;
		for (auto d : dp) {
			for (auto p : d) {
				cout << p << " ";
			}
			cout << endl;
		}*/
	}
	int m = 0;
	for (int i = 0; i < nums.size(); i++)
		m = max(m, dp[i][1]);
	cout << m;
	return 0;
}

### 蓝桥杯 最长下降子序列 Python 实现 #### 问题分析 最长下降子序列问题是经典的动态规划问题之一。它通常可以通过多种方式解决,包括贪心加二分查找、树状数以及线段树等方法[^1]。 --- #### 方法一:基于动态规划的经典实现 以下是通过经典动态规划的方法来求解最长下降子序列的Python代码: ```python def longest_non_decreasing_subsequence_dp(sequence): n = len(sequence) dp = [1] * n # 初始化DP数,默认每个位置的LIS长度至少为1 for i in range(1, n): for j in range(i): if sequence[j] <= sequence[i]: dp[i] = max(dp[i], dp[j] + 1) # 更新当前状态的最大值 return max(dp) # 测试样例 sequence = [10, 9, 2, 5, 3, 7, 101, 18] print(longest_non_decreasing_subsequence_dp(sequence)) ``` 上述代码的时间复杂度为 \(O(N^2)\),适用于较小的数据规模。 --- #### 方法二:贪心加二分查找优化 为了进一步提高效率,可以采用贪心算法结合二分查找的方式降低时间复杂度至 \(O(N \log N)\)[^1]。具体思路如下: - 维护一个列表 `tails`,其中存储的是当前已知的最长上升子序列的最小可能结尾。 - 遍历原序列中的每一个元素,并利用二分查找将其插入到合适的位置上。 下面是对应的Python实现: ```python import bisect def longest_non_decreasing_subsequence_greedy(sequence): tails = [] # 存储当前最优的递增子序列尾部数值 for num in sequence: idx = bisect.bisect_right(tails, num) # 找到最后一个小于等于num的位置 if idx == len(tails): # 如果找到,则扩展新的尾巴 tails.append(num) else: # 否则替换掉该位置上的数 tails[idx] = num return len(tails) # 测试样例 sequence = [10, 9, 2, 5, 3, 7, 101, 18] print(longest_non_decreasing_subsequence_greedy(sequence)) ``` 此方法仅提高了性能,还保持了较高的可读性和简洁性。 --- #### 方法三:线段树高级实现 对于更复杂的场景(如允许修改操作),可以引入线段树结构支持区间查询和更新功能[^4]。下面是一个简单的例子展示如何构建并应用线段树解决问题: ```python class SegmentTree: def __init__(self, size): self.size = size self.tree = [0] * (size << 2) # 创建四倍大小的空间作为线段树节点 def update(self, pos, val, node=1, start=0, end=None): if not end: end = self.size - 1 if start == end: # 叶子结点情况 self.tree[node] = val return mid = (start + end) >> 1 if pos <= mid: self.update(pos, val, node << 1, start, mid) else: self.update(pos, val, (node << 1) | 1, mid + 1, end) self.tree[node] = max(self.tree[node << 1], self.tree[(node << 1) | 1]) def query_max(self, left, right, node=1, start=0, end=None): if not end: end = self.size - 1 if left > end or right < start: # 完全在范围内 return 0 if left <= start and right >= end: # 当前范围完全被覆盖 return self.tree[node] mid = (start + end) >> 1 res_left = self.query_max(left, right, node << 1, start, mid) res_right = self.query_max(left, right, (node << 1) | 1, mid + 1, end) return max(res_left, res_right) def longest_non_decreasing_subsequence_segment_tree(sequence): from sortedcontainers import SortedList unique_vals = SortedList(set(sequence)) # 去重排序后的唯一值集合 segtree = SegmentTree(len(unique_vals)) result = [] for num in sequence: rank = unique_vals.index(num) # 获取当前数字在去重后列表中的排名 prev_max_len = segtree.query_max(0, rank) # 查询小于等于rank的部分最大值 current_length = prev_max_len + 1 segtree.update(rank, current_length) # 更新对应位置的最大长度 result.append(current_length) return max(result) # 测试样例 sequence = [10, 9, 2, 5, 3, 7, 101, 18] print(longest_non_decreasing_subsequence_segment_tree(sequence)) ``` 这种方法特别适合处理带有频繁修改需求的情况,在某些特定题目中有广泛应用价值。 --- #### 性能对比总结 | 方法 | 时间复杂度 | 空间复杂度 | |------------------------|---------------|--------------| | 动态规划 | O() | O(N) | | 贪心+二分 | O(N log N) | O(N) | | 线段树 | O(N log M) | O(M) 或更高 | M 表示数据取值范围内的同数值数量^。 ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值