[NOIP 1999] 导弹拦截
题目描述
有若干枚导弹,每一枚导弹的高度是 \(h_i\) ,导弹拦截系统每次拦截导弹都不能比上一次拦截的高度更高,导弹拦截没有冷却时间且第一次拦截的高度任意。
问题1:一套系统最多能拦截多少导弹?
问题2:拦截所有导弹最少需要多少个拦截系统?
输入格式
一行,若干个整数,表示 \(h_i\) 。
输出格式
两行,分别表示两个问题的答案
题解
对于问题1,实际就是求最长不上升子序列的长度,使用动态规划解决。朴素的动态规划是设置 \(dp_i\) 表示以 \(h_i\) 结尾的子序列最大长度。转移时寻找 \(i\) 之前的最大长度转移,即 \(dp_i = max(dp_j), j \leq i 且 h_j \geq h_i\) 。这种算法的时间复杂度是 \(O(n^2)\) ,无法通过。
考虑优化。对每个导弹的枚举显然无法优化,考虑优化状态转移。该算法的瓶颈在于寻找最大长度时需要枚举,但符合高度限制的导弹分布是零散的,无法根据编号快速查找,因此考虑改变查找的维度。我们的目标是获得以 \(h_i\) 结尾的子序列的最大长度,判断是否能往一个序列上延长只需要考虑该序列的结尾元素,而不难得出最大长度会随着子序列结尾的数的减小而增大。换句话说,在取得子序列最大长度的条件下(不论它们的长度是多少),不可能存在h的子序列序列A的结尾元素比子序列B的结尾元素大并且A的长度比B长的情况。因此我们只需要统计出每个长度的不上升子序列的结尾元素即可,在最优情况下,这个结尾元素应该取可能的情况中的最大值。如此我们就得到了一个有序的,可以 \(O(1)\) 验证可行性的序列,采用二分即可在 \(O(log(n))\) 复杂度内找到最优的状态转移。
对于问题2,实际上就是求最长不上升子序列的最少个数,即最长不上升子序列的最小划分。根据 Dilworth 定理,即对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。在本题中的应用是:最长不上升子序列的最小划分数(个数)等于最长上升子序列的长度。因此在第一问的基础上修改得到最长上升子序列的长度即可解决此题。具体的修改是将结尾元素的最大值替换为最小值、验证可行性从不高于变成高于。
AC代码
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 1e5 + 3;
int n, a[MAXN], dp[MAXN], f[MAXN];
int main() {
while (cin >> a[++n]);
// 求最长不升子序列
for (int i = 0; i <= n; i++) f[i] = 0;
n--;
int ans1 = 0;
dp[1] = 1, f[1] = a[1];
for (int i = 2; i <= n; i++) {
int l = 1, r = n, mid;
while (l <= r) {
mid = (l + r) >> 1;
if (f[mid] >= a[i]) l = mid + 1;
else r = mid - 1;
}
f[r + 1] = max(f[r + 1], a[i]);
dp[i] = r + 1;
ans1 = max(ans1, dp[i]);
}
cout << ans1 << '\n';
// 求最长上升子序列
int ans2 = 0;
for (int i = 0; i <= n; i++) f[i] = 1e6;
dp[1] = 1, f[1] = a[1];
for (int i = 2; i <= n; i++) {
int l = 1, r = n, mid;
while (l <= r) {
mid = (l + r) >> 1;
if (f[mid] < a[i]) l = mid + 1;
else r = mid - 1;
}
f[r + 1] = min(f[r + 1], a[i]);
dp[i] = r + 1;
ans2 = max(ans2, dp[i]);
}
cout << ans2 << '\n';
return 0;
}
标签:结尾,NOIP,1999,题解,mid,序列,长度,拦截,dp
From: https://www.cnblogs.com/Floyd3265/p/18143561