zoukankan      html  css  js  c++  java
  • AcWing 1010. 拦截导弹

    题目传送门

    一、LIS+贪心

    1、贪心策略

    使用尽可能少的导弹拦截系统,拦截所有导弹:

    • 当出现新的敌方导弹时,我方从已有的拦截系统中,选取高度最低且可以拦截该导弹的那套系统,进行拦截。

    • 如果没有能拦截该导弹的拦截系统,则要新增一套拦截系统。

    • 为每一套拦截系统维护着一个数字,描述此套拦截系统最后面的拦截高度是多少,也就是最低高度。

    • 由下一套系统是因为当前数字比前面的数字发生上升而创建的,所以,它肯定比前面的大。

    • 如果再来一个小的,加入到现在数据最小的系统后面,也是最节约成本的,防止大数浪费。这样下面,这个数组就成了单调上升的数组了。

    2、实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;
    int n;      //n个导弹
    int a[N];   //记录导弹飞行高度
    int f[N];   //以f[i]为结尾的连续上升子序列的个数
    int g[N];   //多套导弹防御系统的最后一个高度值(最矮的那个), 只需要记录最矮的即可。
    
    int main() {
        //第一问
        while (cin >> a[n]) n++;//下标从0开始啊~
    
        //正常的LIS求最长不上升子序列
        int res = 0;
        for (int i = 0; i < n; i++) {
            f[i] = 1;
            for (int j = 0; j < i; j++)
                if (a[i] <= a[j])f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }
        printf("%d\n", res);
    
        //第二问,求用几套导弹拦截系统可以完成拦截任务
        int cnt = 0;//使用的导弹拦截系统套数
    
        //遍历每个导弹
        for (int i = 0; i < n; i++) {
            //此导弹使用第几组导弹防御系统进行拦截?从前往后找的序列
            int k;
            //搜索,找到第一个能装下a[i]的序列
            for (k = 0; k < cnt; k++)
                if (g[k] >= a[i]) break;
            //如果没有任何一个序列存在大于等于当前数a[i]的,则需要创建一个新的序列
            if (k == cnt) cnt++;
            //不管是否创建新的序列,都将a[i]放到此防御系统中
            g[k] = a[i];
        }
        //输出
        printf("%d\n", cnt);
        return 0;
    }
    

    4、二分法优化版本

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;
    int n;      //n个导弹
    int a[N];   //记录导弹飞行高度
    int f[N];   //以f[i]为结尾的连续上升子序列的个数
    int g[N];   //多套导弹防御系统的最后一个高度值(最矮的那个), 只需要记录最矮的即可。
    
    int main() {
        //第一问
        while (cin >> a[n]) n++;//下标从0开始啊~
    
        //正常的LIS求最长不上升子序列
        int res = 0;
        for (int i = 0; i < n; i++) {
            f[i] = 1;
            for (int j = 0; j < i; j++)
                if (a[i] <= a[j])f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }
        printf("%d\n", res);
    
        //数组g的每个元素代表一套导弹拦截系统的拦截序列
        //g[i]表示此时第i套导弹拦截系统所拦截的最后一个导弹的高度
        int cnt = 0;
        for (int i = 0; i < n; i++) { //遍历所有的导弹高度
            int k = lower_bound(g, g + cnt, a[i]) - g;
            if (k == cnt) g[cnt++] = a[i];  //a[i]开创一套新拦截系统
            else g[k] = a[i];               //a[i]成为第p套拦截系统最后一个导弹高度
        }
        //输出
        printf("%d\n", cnt);
        return 0;
    }
    

    二、Dilworth定理解法

    1、狄尔沃斯定理

    最长上升子序列的长度\((lis)\)就是能构成的不上升序列的个数

    有了上面的数学定理神器,我们就可解决掉本题了:

    • 最多拦截多少个导弹
      因为拦截系统第一下是可以任意高的,以后只能拦截比它矮的,所以,这是一个最长下降子序列,也就是\(f[i]\)只能由它前面比它高的\(j\)转化而来。

    • 需要多少套拦截系统
      这个问题如果没有上面的数学定理,其实不好回答。有了上面的定理,其实问题转化为求最长上升子序列的长度就是答案。

    2、实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;
    int n;      //导弹数量
    int a[N];   //导弹高度
    int f[N];   //以i结尾的最长下降子序列长度
    int g[N];   //以i结尾的最长上升子序列长度
    int ans1;   //最长下降子序列的长度 max(f[i])
    int ans2;   //最长上升子序列的长度 max(g[i])
    
    /*
    一旦遇到上升的导弹就一定会用第二套导弹系统。没有其他方法能将上升序列的两个导弹一起打下来。
    所以此题也就可以转化到最长下降子序列和最长上升子序列。
    */
    int main() {
        while (cin >> a[n]) n++;
    
        for (int i = 0; i < n; i++) {
            f[i] = 1;
            g[i] = 1;
            for (int j = 0; j < i; j++) {
                if (a[i] <= a[j]) f[i] = max(f[i], f[j] + 1);//最长下降子序列
                else g[i] = max(g[i], g[j] + 1);//最长上升子序列
            }
            ans1 = max(ans1, f[i]);
            ans2 = max(ans2, g[i]);
        }
        //输出
        printf("%d\n", ans1);
        printf("%d\n", ans2);
        return 0;
    }
    
  • 相关阅读:
    回归模型与房价预测
    朴素贝叶斯应用:垃圾邮件分类
    sklearn中的朴素贝叶斯模型及其应用
    朴素贝叶斯
    KMEAMS算法应用:图片压缩与贝叶斯公式理解
    第九周 课堂
    numpy数组及处理:效率对比
    完整的中英文词频统计
    杜-----------前期作业小笔记
    django_apscheduler 0.4.0删除了name字段
  • 原文地址:https://www.cnblogs.com/littlehb/p/15650470.html
Copyright © 2011-2022 走看看