这道题目源自全国青少年信息学奥林匹克联赛(NOIP)2010年普及组的真题,同时在《信息学奥赛一本通》和洛谷平台都有收录。作为经典的动态规划与贪心算法结合题,它考察选手对问题建模、算法选择和优化能力的综合运用。
题目描述的是导弹拦截系统的作战场景:给定敌方导弹的飞行高度序列,要求计算出最少需要多少套拦截系统才能全部拦截。这里涉及到两个核心问题:
这个问题可以转化为寻找最长非递增子序列(LNDS)的长度。标准解法有两种主流思路:
O(n²)动态规划解法:
cpp复制for(int i=1; i<=n; i++){
dp[i] = 1;
for(int j=1; j<i; j++){
if(a[j] >= a[i])
dp[i] = max(dp[i], dp[j]+1);
}
ans = max(ans, dp[i]);
}
O(nlogn)贪心优化解法:
维护一个单调数组,使用upper_bound进行查找和替换:
cpp复制vector<int> seq;
seq.push_back(a[1]);
for(int i=2; i<=n; i++){
if(a[i] <= seq.back())
seq.push_back(a[i]);
else
*upper_bound(seq.begin(), seq.end(), a[i], greater<int>()) = a[i];
}
这个问题实际上是求序列的最少链划分,根据Dilworth定理,这等价于求最长上升子序列的长度。可以采用类似的贪心策略:
cpp复制vector<int> systems;
for(int i=1; i<=n; i++){
auto it = lower_bound(systems.begin(), systems.end(), a[i]);
if(it == systems.end())
systems.push_back(a[i]);
else
*it = a[i];
}
cout << systems.size();
以下是结合两种解法的完整AC代码:
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
int a[MAXN], dp[MAXN];
int main() {
int n = 0;
while(cin >> a[++n]); n--;
// 第一问解法
vector<int> seq1;
seq1.push_back(a[1]);
for(int i=2; i<=n; i++){
if(a[i] <= seq1.back())
seq1.push_back(a[i]);
else
*upper_bound(seq1.begin(), seq1.end(), a[i], greater<int>()) = a[i];
}
cout << seq1.size() << endl;
// 第二问解法
vector<int> seq2;
for(int i=1; i<=n; i++){
auto it = lower_bound(seq2.begin(), seq2.end(), a[i]);
if(it == seq2.end())
seq2.push_back(a[i]);
else
*it = a[i];
}
cout << seq2.size();
return 0;
}
题目没有明确给出导弹数量,需要使用while循环持续读取:
cpp复制while(cin >> a[++n]); n--;
这种写法可以自动处理EOF结束输入,比预先读取n更鲁棒。
upper_bound配合greater<int>()比较器lower_bound可以复用同一个vector来减少内存使用:
cpp复制vector<int> seq;
// 第一问处理后
seq.clear();
// 第二问处理
upper_bound和lower_bound的使用场景建议使用以下测试数据验证:
code复制389 207 155 300 299 170 158 65
正确输出应为:
code复制6
2
如果导弹不仅有高度还有速度和方向属性,问题将升级为三维偏序问题,需要使用更高级的数据结构如CDQ分治。
考虑导弹动态到达的情况,可以研究在线算法版本,可能需要使用优先队列等数据结构。
这类算法还可应用于:
在NOIP赛场上处理此类题目时:
想要深入掌握这类算法问题,建议: