zoukankan      html  css  js  c++  java
  • AcWing 187. 导弹防御系统

    题目传送门

    前导知识 AcWing 1010 拦截导弹

    一、 概述

    该题也是一个典型的最长上升子序列的问题。

    • 分析
      导弹拦截高度要么一直上升要么一直下降。

    有的导弹可以选择上升,有的可以选择下降,不是单纯地问所存在的序列可以划分为多少组上升子序列的问题【狄尔沃斯定理】,所以不能用之前的方法解。

    • 怎么做
      当找不到办法时,考虑使用枚举法(暴力搜索\(dfs\)

    • 如何做
      从问题的解出发,最终问题的答案是有许多单调上升子序列和许多单调下降子序列,那么实际就是对于每个数,来思考将该数放到上升序列中还是下降序列中。

    • 题解

    1. 注意顺序性

    2. 依次枚举每个数

      (1) 先枚举将该数作为单调上升的序列,还是单调下降的序列中

      (2) 如果该数被放到了单调上升的序列中,则枚举将该数放到哪个单调上升的序列后面

      (3) 如果该数被放到了单调下降的序列中,则枚举将该数放到哪个单调下降的序列后面。

      (4) 或者该数成为一个单独的单调序列

    \(up[]\) 存储当前所有上升子序列的末尾元素
    \(down[]\) 存储当前所有下降子序列的末尾元素

    所以这里实际上用到了上一题导弹防御的结果来做,对于多个单调上升(下降)子序列只需要对末尾元素进行考虑即可。这一点真厉害!!!然后这里又可以进行优化。

    • 使用贪心的思想

      \(x\) 尽可能覆盖一个末尾元素大的序列

      因此是一个很强的优化,所以每一步实际上只有两种选择,要么上升,要么下降。

      \(up\)本身是单调的,所以一方面找到即停止,另一方面可以直接考虑用二分

    • 如何进行搜素

    1. 使用\(BFS\), \(BFS\)是宽度优先,需要进行存储(空间太多,不太好剪枝),而且代码写起来比较麻烦

    2. 使用\(dfs\), 一种想法是使用全局变量,第二种方式是迭代加深

    假设现在要把一个数放入一个上升序列,那么一定是所有能放入的上升序列中,最后一个元素最大的那一个。
    其实想想也是,既然每个数字都要放到一个序列中,对于上升序列,肯定是目前越小越有用,既然能放入大的里面,何必浪费一个小的呢
    注意到其实\(up[i]\)按这种策略已经是排好序的了,所以只用找最先碰到的一个就行了

    二、dfs实现代码

    \(dfs\) \(560ms\) 时间\(O(n2^n)\) 空间\(O(n)\)

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 55;
    int n;
    int a[N];
    int up[N], down[N];
    int res;
    
    /*总结:
     1、面向答案编程,问什么问题,就设计dfs的参数是什么。比如本题关注几套拦截系统,su就是上升拦截系统数量,sd就是下降拦截系统数量
     ,两者的和就是答案。
     2、数字适合做为dfs参数进行传送,数组之类的适合静态数组+回溯法处理,一般不建议采用vector,一来是慢,二来是写法麻烦。
    
     * 功能:暴搜所有可能,配合剪枝,找出最少的拦截系统数量
     * @param step 第几个导弹
     * @param su   上升拦截系统的数量,配合up数组使用,表示 len(up[])
     * @param sd   下降拦截系统的数量,配合down数组使用,表示 len(down[])
     */
    void dfs(int step, int su, int sd) {
        if (su + sd >= res) return; //中途发现已经大于等于res的情况,就返回,剪枝
        if (step == n + 1) {        //收集答案
            //本来是:res = min(res, su + sd);
            //简写:
            res = su + sd;//原因是上面那句话把>=res的都返回了,到这里肯定是小于res的,所以上面的min就没有意义
            return;
        }
        // 情况1:将当前数放到上升子序列中
        int k;
        for (k = 0; k < su; k++) if (up[k] < a[step])break;//找到合适位置
    
        //回溯法
        int t = up[k];      //拷贝副本
        up[k] = a[step];    //是多开一套,还是加在某套的后面,两种情况都需要记录下数据
        if (k < su) dfs(step + 1, su, sd);//只更新数据即可,长度不用长大
        else dfs(step + 1, su + 1, sd);//多开一套,up数组长度长大1
        up[k] = t;          //恢复现场
    
        // 情况2:将当前数放到下降子序列中
        for (k = 0; k < sd; k++) if (down[k] > a[step])break;//找到合适位置
        //回溯法
        t = down[k];
        down[k] = a[step];
        if (k < sd) dfs(step + 1, su, sd);
        else dfs(step + 1, su, sd + 1);
        //恢复现场
        down[k] = t;
    }
    
    int main() {
        //多套数据,输入n=0时停止
        while (cin >> n, n) {
            for (int i = 1; i <= n; i++) cin >> a[i];
            //防御系统的最少数量
            res = n;
            //开始深搜,更新res的值
            dfs(1, 0, 0);
            printf("%d\n", res);
        }
        return 0;
    }
    
    

    三、迭代加深

    迭代加深+普通 \(541ms\) 时间\(O(n2^n)\) 空间\(O(n)\)

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 55;
    int n;
    int a[N];
    int up[N], down[N];
    
    /**
     * 功能:迭代加深模板下的业务代码
     * @param depth 限制深度
     * @param step  第几个导弹
     * @param su    拦截上升导弹系统个数
     * @param sd    拦截下降导弹系统个数
     * @return      是否能在规定深度的情况下完成目标
     */
    bool dfs(int depth, int step, int su, int sd) {
        // 深度从0开始,迭代加深套路
        if (su + sd > depth) return false;
        if (step == n + 1) return true;//在规定防御系统深度的情况下,可以满足要求,就是找到了答案
    
        // 枚举上升子序列 up是单调上升的,想要在up中找到第一个比a[step]小的数字
        int k;
        for (k = 0; k < su; k++) if (up[k] < a[step]) break;
        int t = up[k];
        up[k] = a[step];
        //找到合理解,就直接返回;没有找到合理解,那么还需要继续计算,不能返回
        //换句话说就是:中间只允许返回true,不到最后,不允许返回false
        if (k < su && dfs(depth, step + 1, su, sd)) return true;
        else if (dfs(depth, step + 1, su + 1, sd)) return true;
        up[k] = t;
    
        // 枚举下降子序列
        for (k = 0; k < sd; k++) if (down[k] > a[step])break;
        t = down[k];
        down[k] = a[step];
        if (k < sd && dfs(depth, step + 1, su, sd)) return true;
        else if (dfs(depth, step + 1, su, sd + 1)) return true;
        down[k] = t;
        //实在没招了,返回false
        return false;
    }
    
    int main() {
        while (cin >> n, n) {
            //输入
            for (int i = 1; i <= n; i++) cin >> a[i];
            //迭代加深的套路
            //本题中深度可以理解为最大允许的防御导弹系统数量
            int depth = 0;
            while (!dfs(depth, 1, 0, 0)) depth++;
            //迭代加深的深度就是答案
            printf("%d\n", depth);
        }
        return 0;
    }
    
    
  • 相关阅读:
    11
    关于一些问题的解决办法[记录]TF400017
    jdbc一次性采集mysql和oracle的海量数据,5000W+为例
    java 读取配置文件类
    Apache Thrift
    Runtime.getRuntime().exec 类 防止阻塞
    angularjs select
    angularjs
    简单的搭mysql开发环境
    运用java反射
  • 原文地址:https://www.cnblogs.com/littlehb/p/15654801.html
Copyright © 2011-2022 走看看