AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 187. 导弹防御系统【剪枝:迭代加深/全局最小值】    原题链接    中等

作者: 作者的头像   一只野生彩色铅笔 ,  2021-06-04 20:38:25 ,  所有人可见 ,  阅读 3122


43


9

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

题目给定一个长度为 n 的数组 w[n] ,要求我们用最少的上升子序列和下降子序列完全覆盖该数组

求该方案的上升子序列和下降子序列的总个数

解析

本题是对上题AcWing 1010. 拦截导弹的拓展

对于当前元素 w[i] 应该被加入到 上升子序列 还是 下降子序列 我们可以采用 暴力枚举 的方式

如果当前元素 w[i] 我们选择加入到 下降子序列 中,那么具体要加入到哪个 下降子序列 中
可以参考该篇博客 拦截导弹【附贪心证明】

如果加入到 上升子序列 中,那么具体要加入到哪个 上升子序列 中,方法类似加入 下降子序列 的

时间复杂度:$O(n2^n)$ 因此需要用到 迭代加深/维护全局最小值,剪枝 的优化

Code(维护全局最小值)

#include <iostream>
using namespace std;

// 这题是拦截拦截第二问的加强版
// 拦截导弹第二问是只考虑下降序列的方案数,因此直接用贪心搜出最优解即可(证明在那一题的笔记里)
// 这一题,确实要考虑下降和上升两种序列的方案数
// 因此只能用dfs进行爆搜两种方案的搭配,但无论是上升还是下降方案,依然采用上一题的贪心思路

const int N = 55;
int n;
int a[N];
int up[N], down[N];
int res;

//三个参数分别是考虑前u个导弹
//已经采用了上升系统个数sum_up
//和下降系统个数sum_down
void dfs(int u, int sum_up, int sum_down) {
    //如果已经超过了最优解答案,那么直接剪枝
    if (sum_up + sum_down >= res) return;
    //没有超过最优解答案,且把所有导弹都考虑到了
    //那他就是当前最优解了
    if (u == n) {
        res = sum_up + sum_down;
        return;
    }
    //情况一:考虑用上升拦截系统来拦截第u个导弹
    // 上升拦截系统的贪心思路是:
    //     如果当前已有的上升拦截系统的高度都大于第u个导弹高度,则重新开一套系统
    //     否则,则由当前低于第u个导弹最高拦截系统来负责拦截
    int k = 0;
    while (k < sum_up && up[k] >= a[u]) ++k;
    //找到了有这么个拦截系统
    int t = up[k]; //t用于dfs回溯的时候恢复现场
    up[k] = a[u];
    if (k >= sum_up) dfs(u + 1, sum_up + 1, sum_down);
    else dfs(u + 1, sum_up, sum_down);
    //恢复现场
    up[k] = t;

    //情况二:考虑用下降拦截系统来拦截第u个导弹
    // 下降拦截系统的贪心思路是:
    //     如果当前已有的下降拦截系统的高度都小于第u个导弹高度,则重新开一套系统
    //     否则,则由当前大于第u个导弹最低拦截系统来负责拦截
    k = 0;
    while (k < sum_down && down[k] <= a[u]) ++k;
    t = down[k]; //t用于dfs回溯的时候恢复现场
    down[k] = a[u];
    if (k >= sum_down) dfs(u + 1, sum_up, sum_down + 1);
    else dfs(u + 1, sum_up, sum_down);
    //恢复现场
    down[k] = t;
}

int main() {
    while (cin >> n, n) {
        for (int i = 0; i < n; ++i) cin >> a[i];
        //最差情况是n个导弹分别用n个系统拦截
        //因此可以设置res初始为n来设立哨兵
        res = n;
        dfs(0, 0, 0);
        cout << res << endl;
    }
    return 0;
}

Code(迭代加深)

#include <iostream>
using namespace std;

const int N = 55;
int n;
int w[N];
int up[N], down[N];

bool dfs(int u, int sum_up, int sum_down, int max_depth) {
    if (sum_up + sum_down > max_depth) return false;
    if (u == n) return true;

    int k = 0;
    while (k < sum_up && up[k] >= w[u]) ++ k;
    int t = up[k];
    up[k] = w[u];
    if (k == sum_up && dfs(u + 1, sum_up + 1, sum_down, max_depth)) return true;
    else if (k < sum_up && dfs(u + 1, sum_up, sum_down, max_depth)) return true;
    up[k] = t;

    k = 0;
    while (k < sum_down && down[k] <= w[u]) ++ k;
    t = down[k];
    down[k] = w[u];
    if (k == sum_down && dfs(u + 1, sum_up, sum_down + 1, max_depth)) return true;
    else if (k < sum_down && dfs(u + 1, sum_up, sum_down, max_depth)) return true;
    down[k] = t;

    return false;
}

int main() {
    while (cin >> n, n) {
        for (int i = 0; i < n; ++i) cin >> w[i];
        int depth = 0;
        while (!dfs(0, 0, 0, depth)) ++ depth;
        cout << depth << endl;
    }
    return 0;
}

22 评论


用户头像
6677   2023-12-12 11:30      1    踩      回复

//如果已经超过了最优解答案,那么直接剪枝
if (sum_up + sum_down >= res) return;
//没有超过最优解答案,且把所有导弹都考虑到了
//那他就是当前最优解了
if (u == n) {
res = sum_up + sum_down;
return;
}
请问这两段代码为什么不能互换位置呢

用户头像
JSJBY   2024-05-13 16:07         踩      回复

换了应该加个min取最小值


用户头像
张昂之.   2021-09-30 21:18      1    踩      回复

大佬,你的第一份代码现在好像不行了,在第二个测试样例会WA


用户头像
厄斐琉斯   2024-12-28 12:54         踩      回复

为什么这个需要回溯啊,不是按顺序找到第一个小于的就可以吗?


用户头像
盖上被被睡觉觉   2022-10-05 22:38         踩      回复
  if (k >= sum_up) dfs(u + 1, sum_up + 1, sum_down);
大佬请问这里为啥k取等的时候   要新开一个空间啊(up+1)
用户头像
nope_ck   2022-10-20 20:41         踩      回复

因为这个时候up数组已经新放了一个元素他的边界是[0,sum_up) 所以新扩展了一个值要加一个空间


用户头像
小风扇v   2022-09-06 21:28         踩      回复

大佬请问一下那个贪心的过程能不能二分呀,这样复杂度是不是就降了一点

用户头像
nope_ck   2022-10-20 21:00         踩      回复

可以的


用户头像
一塌糊涂   2022-03-19 09:07         踩      回复

铅笔佬tql


用户头像
Kevin_Durant   2021-10-19 11:05         踩      回复

彩笔大佬写得好啊


用户头像
张昂之.   2021-09-30 21:19         踩      回复

刚刚看了一下,是第一份代码中的dfs(1,0,0),第一个参数要改成0,才能AC

用户头像
一只野生彩色铅笔   2021-09-30 22:35         踩      回复

谢谢指出,已更改


用户头像
Laurus_1   2021-09-22 20:33         踩      回复

迭代加深这个和第一个比有啥好处吗

用户头像
一只野生彩色铅笔   2021-09-22 21:06      1    踩      回复

当已知答案的深度很小时,迭代加深优于全局最小值

搜索的剪枝本身就是一个玄学,全凭出数据的同学心情hh


用户头像
算法先和初中生同步   2021-08-06 15:34         踩      回复

剪枝后的时间复杂度大概怎么判断呢orz

用户头像
一只野生彩色铅笔   2021-08-06 15:45         踩      回复

迭代加深可以按照优化空间的宽搜来计算时间复杂度,深度与答案的值相关

递归的剪枝复杂度太玄学了hh,我掌握的也不太好

如果你想深究的话,可以了解一下 主定理 (Master Theorem) 是专门计算递归复杂度的

用户头像
算法先和初中生同步   2021-08-06 15:48    回复了 一只野生彩色铅笔 的评论         踩      回复

好的,谢谢


用户头像
弈剑观星   2021-07-16 00:50         踩      回复

大佬,y总代码里判断当前元素添加到上升序列还是下降序列,用的是大于等于和小于等于,应该不符合题目中要求的严格单调上升和严格单调下降的要求吧?这题的数据还是少了,两种判断都可以ac

用户头像
一只野生彩色铅笔   2021-07-16 09:57         踩      回复

是的,我也没注意到题目里 严格 二字,确实是数据弱了

用户头像
弈剑观星   2021-07-16 12:35    回复了 一只野生彩色铅笔 的评论         踩      回复

好的,谢谢大佬

用户头像
max.D.well   2021-07-21 10:31         踩      回复

题目说了是n个不同的整数吧

用户头像
弈剑观星   2021-07-22 00:12    回复了 max.D.well 的评论         踩      回复

有道理,还是没有仔细看题啊,谢谢了


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息