【四】【算法】P1020 [NOIP1999 提高组] 导弹拦截,Dilworth 定理 推论,最长递增子序列长度,最长非递增子序列长度,最少递增子序列个数,最少非递增子序列个数,
P1020 [NOIP1999 提高组] 导弹拦截
[NOIP1999 提高组] 导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
一行,若干个整数,中间由空格隔开。
输出格式
两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
样例 #1
样例输入 #1
389 207 155 300 299 170 158 65样例输出 #1
6 2提示
对于前 50\% 数据(NOIP 原题数据),满足导弹的个数不超过 10^4 个。该部分数据总分共 100
分。可使用\mathcal O(n^2) 做法通过。 对于后 50\% 的数据,满足导弹的个数不超过 10^5 个。该部分数据总分也为 100 分。请使用 \mathcal O(n\log n) 做法通过。对于全部数据,满足导弹的高度为正整数,且不超过 5\times 10^4。
此外本题开启 spj,每点两问,按问给分。
NOIP1999 提高组 第一题
\text{upd 2022.8.24}:新增加一组 Hack 数据。
代码
#include<bits/stdc++.h>
using namespace std;
vector<int> a;
vector<int>dp_rise;
vector<int>dp_decline;
/*
代码逻辑没有问题, 但是时间复杂度太大了!
最长非上升和最长上升的区别
前者包括等于,后者严格递增
*/
#if 0
int main() {
int nums;
while (cin >> nums) {
a.push_back(nums);
}
int n = a.size();
dp_rise.resize(n, 1);
dp_decline.resize(n, 1);
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (a[j] < a[i]) {
dp_rise[i] = max(dp_rise[j] + 1, dp_rise[i]);
} else if (a[j] >= a[i]) {
dp_decline[i] = max(dp_decline[j] + 1, dp_decline[i]);
}
}
}
int max_rise = *max_element(dp_rise.begin(), dp_rise.end());
int max_decline = *max_element(dp_decline.begin(), dp_decline.end());
cout << max_decline << " " << max_rise << endl;
}
#endif
/*
利用Dilworth 定理解决最长上升非上升子序列,最长下降非下降子序列和最少子序列个数问题
最长非上升子序列的长度 等价于 最少的上升子序列划分的个数
最长上升子序列的长度 等价于 最少的非上升子序列划分的个数
下降同理,而我们计算 最少的上升子序列划分的个数 或者 最少的非上升子序列划分的个数 更加容易
目标:非上升长度和非上升个数
等价于 上升个数和非上升个数
> <=
*/
#if 1
int main() {
int nums;
while (cin >> nums) {
a.push_back(nums);
}
int n = a.size();
//rise 非上升序列长度<=>最少上升序列个数 > 1 2 3 4 3 4 5 2 3 4 存储的是严格递增最后一个数字
//decline 最少非上升序列个数 <= 4 3 2 2 4 3 2 2
for (int i = 0; i < n; i++) {
auto it = upper_bound(dp_rise.begin(), dp_rise.end(), a[i],greater<int>());//<
auto it2 = lower_bound(dp_decline.begin(), dp_decline.end(), a[i]);//>=
if (it == dp_rise.end()) {
dp_rise.push_back(a[i]);
} else {
*it = a[i];
}
if (it2 == dp_decline.end()) {
dp_decline.push_back(a[i]);
} else {
*it2 = a[i];
}
}
cout << dp_rise.size() << " " << dp_decline.size() << endl;
}
#endif
cpp

-
题目含义
题目涉及我们计算一个子序列中的最大不升序长度以及最少数量的不升序数量. -
思路
根据Dilworth定理的相关推论可知,在该问题中,“最大不增长连续子序列的数量”与“最少增长连续区间的划分数量”之间存在直接对应关系。由此可知,在解决这一类问题时,“最小数量的不增长区间划分”实际上可以通过“寻找最长增长连续子序列”的方法来确定。
对于计算“最大增长连续子序列的长度”以及“最大不增长连续区间的数量”,都可以采用动态规划的方法进行有效求解。“此外,在处理这类优化问题时,“最大化增长性特征”的指标通常可以通过计算相应的划分数量来实现。”
非递增的子序列以及递增的子序列各自指的是其后续元素与前一元素之间的关系:前者要求后续元素不大于前一元素;后者则要求后续元素严格大于前一元素。

- 时间复杂度
其涉及动态规划的时间计算结果为O(n²),这将导致运行出现超时问题.
采用贪心算法与二分法结合使用能够有效减少计算所需资源. - 时间复杂度
动态规划的时间计算结果较为显著,可能会引起程序运行超时.
应用贪心算法并配合使用二分查找方法能够明显降低计算效率.
求长度 动态规划
int main() {
int nums;
// 读取输入直到输入结束
while (cin >> nums) {
a.push_back(nums); // 将每个数字加入序列
}
int n = a.size(); // 获取序列的大小
// 初始化 dp 数组,所有元素初始值为 1,因为每个元素自身也可以作为一个长度为 1 的子序列
dp_rise.resize(n, 1); // 最长上升子序列的长度数组
dp_decline.resize(n, 1); // 最长下降子序列的长度数组
// 双重循环来计算 dp 数组
for (int i = 0; i < n; i++) {
// 对于每个 a[i],检查它前面的所有元素 a[j]
for (int j = 0; j < i; j++) {
if (a[j] < a[i]) {
// 如果 a[j] < a[i],说明 a[i] 可以接在 a[j] 之后,形成一个更长的上升子序列
dp_rise[i] = max(dp_rise[j] + 1, dp_rise[i]);
} else if (a[j] >= a[i]) {
// 如果 a[j] >= a[i],说明 a[i] 可以接在 a[j] 之后,形成一个更长的下降子序列
dp_decline[i] = max(dp_decline[j] + 1, dp_decline[i]);
}
}
}
// 找到 dp_rise 和 dp_decline 中的最大值
int max_rise = *max_element(dp_rise.begin(), dp_rise.end()); // 最长上升子序列的长度
int max_decline = *max_element(dp_decline.begin(), dp_decline.end()); // 最长下降子序列的长度
// 输出结果,先输出最长下降子序列的长度,再输出最长上升子序列的长度
cout << max_decline << " " << max_rise << endl;
return 0;
}
cpp


- max_element
C++ 标准库中提供了该函数max_element,它负责返回给定范围内的最大元素的迭代器。
通过调用std::*max_element(),你可以获得该范围的最大值。
求个数 贪心+二分
-
目标
本题涉及我们在一个子序列中寻求其最大非递增自反长度以及最少的自反数目。
转换成数量上分为最少增量度与最少自反度。- 最小递增子序列个数

- upper_bound和lower_bound
该函数用于在有序范围内找到第一个 大于指定值 的元素的位置.
使用greater<int>()参数则可确定 小于指定值 的位置.
iterator upper_bound(iterator first, iterator last, const T& value);
iterator upper_bound(iterator first, iterator last, const T& value, Compare comp);
auto it = upper_bound(dp_rise.begin(), dp_rise.end(), a[i], greater<int>());
//找严格小于
cpp
该函数用于在有序序列中找到第一个不小于指定值的那个元素的位置。通过调用greater
iterator lower_bound(iterator first, iterator last, const T& value);
iterator lower_bound(iterator first, iterator last, const T& value, Compare comp);
auto it = lower_bound(v.begin(), v.end(), a[i], greater<int>());
//找小于等于
cpp
结尾
最后一篇推送的文章发布后不久(段落保持不变),作者衷心感谢您的关注与阅读(段落保持不变)。希望这些内容能给您带来一些启发与帮助(句子结构变换)。如有任何疑问或想法,请随时在评论区与我交流(避免使用过于生硬的表达)。(注:此处删除了"请随时"中的"请"字以使其更加自然流畅)与此同时(换用更贴近日常口语的表达),别忘了订阅我们的公众号哦!这样不仅能第一时间获取最新内容更新通知。(注:此处将原文中的"同时"改为"与此同时"并增加了具体操作建议)。(段落保持不变)在未来的文章中(换用更积极向上的表述),作者将继续探讨这个话题的不同方面(避免使用重复的表达),为您呈现更多深度和见解。(避免使用过于生硬的措辞)感谢您的关注与支持(换用更亲切的表达),期待下次再见于文章讨论中!
