[线性dp] 导弹防御系统(最长上升子序列模型+贪心+dfs)

15 篇文章 0 订阅
12 篇文章 0 订阅

0. 前言

强相关:

[线性dp] 最长上升子序列(模板题+最长上升子序列模型) 贪心解法

[线性dp] 拦截导弹(最长上升子序列模型+贪心)

1. LIS +贪心+dfs

187. 导弹防御系统

在这里插入图片描述

重点: 线性 dpLIS 问题、贪心

思路:

  • [线性dp] 拦截导弹(最长上升子序列模型+贪心)是高度只能不断下降。本题高度可以不断上升和不断下降,但需要维护其单调性。即,最少使用多少个上升、下降子序列将整个序列覆盖掉。
  • [线性dp] 拦截导弹(最长上升子序列模型+贪心)我们只需要从前往后考虑每一枚导弹,采用贪心的思路,每次就只有在上升子序列中对两种情况进行决策。而本题就有两种情况,需要先划分该导弹是上升子序列还是下降子序列
  • 本题没什么好的方法,只能通过暴力枚举当前导弹放到上升、下降的子序列中的所有情况取最优值即可
  • dfs 暴搜一般是 2 n 2^n 2n 的计算量
  • 本题可抽象为 dfs 求最小步数问题,通常有两种解决方式
    • 记全局最小值
    • 迭代加深
  • dfs 为什么较于 bfs 更加常用?
    • bfs 空间容易爆炸,因为需要将这一层的状态都存下来,空间为指数级别
    • dfs 递归时只存储路径,空间为线性级别
    • dfs 可以剪枝,bfs 很难剪枝

[线性dp] 最长上升子序列(模板题+最长上升子序列模型)LIS 贪心+二分的思路强相关。

这里 n=55, 2^55 这个计算量为什么不会超时呢

  • 首先答案很小,也就是需要用到的单调序列的个数很少,因此 dfs() 中的 depth 这个参数就很小,那么 if (su + sd > depth) return false; 这个剪枝就会剪掉很多分支了。

dfs全局最小值代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 55;

int n;
int q[N];
int up[N], down[N];
int ans;

// u:当前枚举到那个数,su:最小上升子序列个数,sd最小下降子序列个数
void dfs(int u, int su, int sd) {   
    if (su + sd >= ans) return ;    // su和sd不会再将答案变小,故剪枝,以此找到最小值
    if (u == n) {
        ans = su + sd;      // 枚举结束,直接更新,因为su+sd>n的情况已经提前return了
        return ;
    }
    
    // 情况1:将当前数放到下降子序列中
    int k = 0;
    while (k < su && up[k] >= q[u]) k ++;
    int t = up[k];                  // 现场备份,方便回溯时恢复现场
    up[k] = q[u];                   
    if (k < su) dfs(u + 1, su, sd);
    else dfs(u + 1, su + 1, sd);
    up[k] = t;                      // 恢复现场
    
    // 情况2:将当前数放到上升子序列中
    k = 0;
    while (k < sd && down[k] <= q[u]) k ++;
    t = down[k];
    down[k] = q[u];
    if (k < sd) dfs(u + 1, su, sd);
    else dfs(u + 1, su, sd + 1);
    down[k] = t;                    // 恢复现场
}

int main() {
    while (cin >>n, n) {
        for (int i = 0; i < n; ++i) cin >> q[i];
        
        ans = n;        // 最坏情况每个数单独一个序列,则最大为n
        dfs(0, 0, 0);
        cout << ans << endl;
    }
    return 0;
}

贴一份y总的dfs迭代加深代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 60;

int n;
int h[N];
int up[N], down[N];

bool dfs(int depth, int u, int su, int sd)
{
    // 如果上升序列个数 + 下降序列个数 > 总个数是上限,则回溯
    if (su + sd > depth) return false;
    if (u == n) return true;

    // 枚举放到上升子序列中的情况
    bool flag = false;
    for (int i = 1; i <= su; i ++ )
        if (up[i] < h[u])
        {
            int t = up[i];
            up[i] = h[u];
            if (dfs(depth, u + 1, su, sd)) return true;
            up[i] = t;
            flag = true;
            break;  // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了
        }
    if (!flag)  // 如果不能放到任意一个序列后面,则单开一个新的序列
    {
        up[su + 1] = h[u];
        if (dfs(depth, u + 1, su + 1, sd)) return true;
    }

    // 枚举放到下降子序列中的情况
    flag = false;
    for (int i = 1; i <= sd; i ++ )
        if (down[i] > h[u])
        {
            int t = down[i];
            down[i] = h[u];
            if (dfs(depth, u + 1, su, sd)) return true;
            down[i] = t;
            flag = true;
            break;  // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了
        }
    if (!flag)  // 如果不能放到任意一个序列后面,则单开一个新的序列
    {
        down[sd + 1] = h[u];
        if (dfs(depth, u + 1, su, sd + 1)) return true;
    }

    return false;
}

int main()
{
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i ++ ) cin >> h[i];

        int depth = 0;
        while (!dfs(depth, 0, 0, 0)) depth ++ ;     // 迭代加深搜索

        cout << depth << endl;
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值