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;
    }
    
  • 相关阅读:
    动态传参
    函数的介绍
    文件的操作
    send email with formatted table
    minimize and close window with customed winform
    python algorithm
    something important about docker
    book list
    which language is suitable for what to do
    Find Duplicate Items in list fast
  • 原文地址:https://www.cnblogs.com/littlehb/p/15650470.html
Copyright © 2011-2022 走看看