关于最长上升子序列的nlogn时间复杂度的DP算法详解(易懂)

受启发于:https://www.cnblogs.com/wxjor/p/5524447.html

在学习DP时相信我们都遇到过这一道题:最长上升子序列,这道题用DP来解的话时间复杂度为n^2;
这在n大概是10000,而时间限制为1秒时是可以被接受的,但是当n取到100000时我们就不能只用简单的DP来做了

这个时候我们可以使用另一种DP,那就是对长度DP

我们建立一个数组DP[100001];

DP[i]表示能使上升序列长度为i时的所有序列中的最右边的数字的最小值

比如序列 2 3 4 1 5 8 6,很显然,最长上升子序列长度为5,分别为 2 3 4 5 6或者 2 3 4 5 8

那么DP[1…5]={1 3 4 5 6}

因为长度为1时,我们可以取任意数当序列,所以很明显1是最小的,所以DP[1]=1。

当长度为2时,我们可以取的序列有 {2,3} {2,4} {2,5},{2,8}…等等,很明显所有序列中的最右边的数字的最小值为3,所以DP[2]=3;

依次类推

这个数列有什么用呢

如果我们已经有了这个DP数列,我得到了一个新数字我该怎么处理呢,比如10

很明显,如果它比6大,说明,我们可以得到序列 2 3 4 5 6 10,所以我们就可以将答案和DP数组的长度更新为6,同时DP[6]=10
既DP[1…6]={1 3 4 5 6 10}
依次类推

但是如果我们得到一个新数字不能比DP最右边的数字大呢,比如这个数字是2的时候, 我们该怎么办呢,

我们当然不能忽略这个数字,当它等于2的时候,我们可以知道,我们可以得到上升序列为{1 2},所以DP[2]应该等于2才对
所以我们就可以更新DP数列DP[1…6]={1 3 4 5 6 10},更新为DP[1…6]={1 2 4 5 6 10}

那有人会问,我们更新DP数列有什么用?
那是肯定有用啊 ,因为我们通过不断的更新以后,我们会把DP数组元素变得尽可能地小,导致我们可能会把最右边的数字更新得更小,使得增加长度更容易,这很棒不是吗?

比如说来了一个新的数字9的话,它比10小,那么我们就可以把10更新为9,既DP[1…6]={1 2 4 5 6 9}

增加的方法很容易,那我们的更新方法是什么?

最重要的来了:
最重要的来了:
最重要的来了:

我们更新DP数组的方法是从从到右找到第一个比他大的数字,然后把它替换掉

如果我们傻傻地从左往右找,时间复杂度仍然为n^2,欧,功亏一篑

一拍脑袋,既然DP数组是有序递增的,那么我们为什么不使用二分查找呢

所以,我们将上面的步骤用代码表示出来的时候是这样的:

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 2e6 + 1;
int dp[maxn];
int main() {
	int n,num,ans = 0;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> num;
		if (i == 1 || num > dp[ans - 1]) { dp[ans] = num; ans++; }
		else {
			auto q = lower_bound(dp, dp + ans + 1,num);
			*q = num;
		}
	}
	cout << ans << endl;
	return 0;
}

### CCPC 2023 H题 解析 关于CCPC 2023 H题的具体题目描述尚未公开,但从以往的比赛惯例以及类似的题目解析可以推测其可能涉及的内容和技术要点。以下是基于已有参考资料和专业知识对该类问题的解答框架。 #### 1. **问题背景** CCPC(Chinese Collegiate Programming Contest)作为国内重要的编程竞赛之一,通常会设计具有挑战性的算法问题来测试参赛者的逻辑思维能力和编码技巧。H题通常是比赛中的难点之一,往往涉及到复杂的算法模型或数据结构的应用[^2]。 #### 2. **潜在的技术方向** 根据过往的经验,H题可能会覆盖以下几个方面: - 动态规划 (Dynamic Programming)[^1] - 构造性问题 (Construction Problems)[^4] - 数学优化 (Mathematical Optimization) 假设该题属于动态规划类别,则需关注状态转移方程的设计;如果是构造性问题,则重点在于如何通过有限操作达到目标条件。 #### 3. **通用解题策略** 无论具体主题为何种类型,在面对高难度赛题时可遵循如下方法论: ##### (1)深入理解题目需求 仔细阅读并反复确认输入输出的要求及其约束条件,确保不会遗漏任何细节信息[^3]。 ##### (2)选取合适的算法工具箱 依据实际场景挑选最匹配的方法论,比如当存在重叠子问题且具备最优子结构性质时优先选用DP技术[^1]。 ##### (3)编写清晰易懂的代码实现 采用模块化的方式分步完成整个功能开发流程,并辅以充分注释说明每一部分的作用机制。 ```cpp // 示例伪代码片段展示基本框架布局 #include <bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n; cin >> n; // 输入规模参数 vector<long long> dp(n+1, INF); // 初始化dp数组,默认极大值表示未访问过 dp[0]=0; for(int i=1;i<=n;i++){ for(auto &coin : coins){ if(i >= coin && dp[i - coin]+costs[coin]<dp[i]){ dp[i]=dp[i - coin]+costs[coin]; } } } cout << (dp[n]==INF ? -1 : dp[n])<< "\n"; } ``` 上述例子仅作示意用途,真实情况下应严格依照官方给定的数据范围调整变量类型及边界处理方式。 #### 4. **复杂度考量** 对于大规模实例而言,效率至关重要。因此除了正确率之外还需兼顾运行时间和内存消耗指标。一般建议尽可能降低渐近时间开销至O(NlogN)甚至更低级别。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值