--- title: "拦截导弹" created: 2025-11-28 tags: - 算法 --- # 拦截导弹 ## 题目 [拦截导弹](https://www.acwing.com/solution/content/52042/) 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。 但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。 某天,雷达捕捉到敌国的导弹来袭。 由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。 输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。 **输入格式** 共一行,输入导弹依次飞来的高度。 **输出格式** 第一行包含一个整数,表示最多能拦截的导弹数。 第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。 **数据范围** 雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。 **输入样例:** 389 207 155 300 299 170 158 65 **输出样例:** 6 2 ## 思路分析 题意分析一下大概就是问 输入的数据 有多少组下降子序列 然后最长下降子序列的长度是多少(严谨一点应该是不上升 可以持平) 1、该数组的最长不上升子序列 2、该数组最少能被几个最长不上升子序列全部覆盖 ![[image-1d9bddbc.png]] 第二个问题的证明: 要求我们用最少的最长下降子序列对原数组进行全覆盖 考虑一种贪心方案: 对于第i个数来说,把它加入前 i - 1 个数构成的下降子序列组中,所有结尾元素大于第i个数的数中最小的那个数 证明:(最优解 = 贪心解) 假设存在一个最优解,他在考虑第 i 个数放入的下降子序列组中,选择了贪心解方案的后面的一个位置 具体如图所示:(绿色部分,更新了q[i+1]后为保证递增顺序,交换了q[i]和q[i+1],这一步省略了) ![[image-c3c02a70.png]] 可以观察到,该最优策略使得当前局面差于贪心策略,即能接在(q[i],q[i+1])范围的子序列少了一个 即贪心解 ≤最优解 同理可证,最优策略在考虑第 i 个数放入的下降子序列组中,选择了贪心解方案的后面的第 k个位置也有结论贪心解 ≤最优解 此外,由于贪心解是合法解,所以必然 贪心解 ≥最优解 于是有 贪心解 =最优解 证明:(调整法) 假设存在一种最优策略,不是按照贪心方案进行阶段决策的 则我们可以通过有限次的调整,把最优解调整成贪心解的方案,具体如下图所示 ![[image-5a4c9cb3.png]] 于是,由该决策包容性,得出最优解可以是贪心解。 基本思想是尝试将每个来袭的导弹高度放入已有的系统中,如果当前导弹高度不能放入任何一个系统(即当前导弹的高度大于所有系统的当前可接受的最大高度),则需要增加一个新的系统。维护一个数组`q`记录每个系统可以接受的最大高度,对于每个导弹,使用二分查找在`q`中找到可以放入的位置,并更新该系统的可接受的最大高度。如果找不到合适的系统,则开启一个新的系统。最终`q`的长度(即`cnt`的值)即为所需的最小系统数。 从前往后做一遍最长下降子序列,同时维护一个数组长度为cnt的单调不减数组q[N] 数组 q[N] 中每个元素维护的是当前以 q[i] 结尾的下降子序列 于是,对于第 i 个元素来说,他能插入的到 q[N] 中的哪个下降子序列中,是存在一个二分性质的 由于要求的是下降子序列且 q[N] 是单调不减的 因此对于所有的 w[i] ,必然存在一个边界 j ,满足∀k∈[0,j),有q[k] using namespace std; const int N = 1010, INF = 30010; int n, x; int w[N], f[N]; // w数组存储导弹高度,f数组用于DP求最长不下降子序列 int q[N], cnt; // q数组存储每个系统可以接受的最大高度,cnt记录系统数量 int main() { while(cin>>x) w[++n] = x; // 输入导弹高度 // 第一问:计算最多能拦截的导弹数 int res = 0; for(int i=1;i<=n;i++) { f[i]=1; // 初始化当前导弹的最长不下降子序列长度为1 for(int j=0;j=w[i]) // 如果前面的导弹高度不低于当前导弹 f[i]=max(f[i], f[j]+1); // 更新最长不下降子序列长度 } res = max(res, f[i]); // 更新全局最大值 } cout << res << endl; // 第二问:计算拦截所有导弹最少需要配备的系统数 for(int i = 1;i <= n;i++) { int l=0, r=cnt; // 二分查找的范围 while (l < r) { int mid =l+r>>1; if(q[mid]>=w[i]) r = mid; // 找到一个系统,其可以接受的最大高度大于等于当前导弹高度 else l = mid + 1; } // 说明找到的q[r]仍然小于w[i],即当前导弹高度大于所有系统的当前可接受的最大高度 if (q[r] < w[i]) r ++ ; // 需要增加一个新系统 cnt = max(cnt, r); // 更新系统数量 q[r] = w[i]; // 更新该系统可接受的最大高度为当前导弹高度 } cout << cnt << endl; // 输出最少需要的系统数 return 0; } ``` ```cpp #include using namespace std; int maxIntercepts = 0; // 全局变量,记录单次操作中最多能拦截的导弹数 // 函数:拦截导弹并更新剩余导弹序列 void intercept(vector& missiles) { if (missiles.empty()) return; vector dp(missiles.size(), 1); // dp[i] 表示以 missiles[i] 结尾的最长不上升子序列的长度 for (int i = 0; i < missiles.size(); ++i) { for (int j = 0; j < i; ++j) { if (missiles[i] <= missiles[j]) { dp[i] = max(dp[i], dp[j] + 1); } } maxIntercepts = max(maxIntercepts, dp[i]); // 更新在所有拦截操作中最多能拦截的导弹数 } // 识别并移除这一轮中拦截的导弹 vector nextRound; vector toRemove(missiles.size(), false); int maxLength = *max_element(dp.begin(), dp.end());//找出给定范围内(这里是dp向量的全部元素)的最大元素的值 int removeCount = maxLength; for (int i = missiles.size() - 1; i >= 0 && removeCount > 0; --i) { if (dp[i] == removeCount) { toRemove[i] = true; // 标记此导弹被拦截 --removeCount; } } // 构建下一轮剩余的导弹序列 for (int i = 0; i < missiles.size(); ++i) { if (!toRemove[i]) nextRound.push_back(missiles[i]); } missiles = nextRound; // 更新为下一轮的导弹序列 } int main() { vector missiles; int height; // 输入导弹高度序列 while (cin >> height) { missiles.push_back(height); } int systemsNeeded = 0; // 记录需要的系统数 // 循环,直到所有导弹都被拦截 while (!missiles.empty()) { intercept(missiles); // 执行拦截操作,并更新剩余导弹序列 ++systemsNeeded; // 每进行一次拦截操作,需要的系统数加1 } // 输出结果 cout << maxIntercepts << endl; // 最多能拦截的导弹数 cout << systemsNeeded << endl; // 最少需要的系统数 return 0; } ``` ## 同类题型 ## 视频讲解 --- ⬅️ [[怪盗基德的滑翔翼|怪盗基德的滑翔翼]] 🏠 [[00-刷题理模型]] ➡️ [[接龙序列|接龙序列]]