题目传送门

前导知识 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;
}

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-12-13
  • 2022-12-23
  • 2021-11-11
  • 2021-09-02
  • 2021-05-05
  • 2021-07-03
猜你喜欢
  • 2021-05-28
  • 2021-12-03
  • 2022-12-23
  • 2022-01-29
  • 2021-09-06
  • 2022-02-17
  • 2021-09-03
相关资源
相似解决方案