AcWing 1010. 拦截导弹


题目传送门

一、Dilworth定理解法

1、Dilworth定理

最少的下降序列个数就等于整个序列最长上升子序列的长度

背下来这个定理结论吧!!!

2、实现代码

#include 

using namespace std;
const int N = 1010;
int n;      //导弹数量
int a[N];   //导弹高度
int f[N];   //以f[i]为结尾的最长不上升子序列,值为个数
int g[N];   //以g[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;
}

二、LIS+贪心

1、贪心策略

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

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

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

2、g数组为什么是单调递增的?

输入样例:
389 207 155 300 299 170 158 65

我们来模拟一下\(g\)数组变化情况:
(1) 讨论\(389\) \(g[0]=389\)
(2) 讨论\(207\) \(g[0]=207\)
(3) 讨论\(155\) \(g[0]=155\)

(4) 讨论\(300\) \(g[0]=155\)后面无法放下\(300\),需要再开启一个新的位置!
注意:这里首次放入的\(300\),肯定是大于\(155\)的,否则不用开启新的位置!!!
\(g[0]=155\) \(g[1]=300\)

(5)讨论\(299\) \(g[0]=155\) \(g[1]=299\)
\(299\)之所以能替换了\(300\),是因为它无法放在\(155\)的后面!整个数组的单调性不变!

(6)讨论\(170\) \(g[0]=155\) \(g[1]=170\)

(7)讨论\(158\) \(g[0]=155\) \(g[1]=158\)

(8)讨论\(65\) \(g[0]=65\) \(g[1]=158\)
\(65\)之所以能替换掉\(155\),是因为它从左到右第一个就发现了合适的位置,使\(g[0]\)变的更小!整个数组的单调性不变!

结论:\(g\)数组是一个单调上升的序列。

3、实现代码

#include 

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 = 0;
        //搜索,因为g数组是单高的,所以找到第一个能装下a[i]的序列,就是目前导弹的最小高度~
        while (k < cnt && g[k] < a[i])k++;
        //如果没有任何一个序列存在大于等于当前数a[i]的,则需要创建一个新的序列
        if (k >= cnt) cnt++;
        //不管是否创建新的序列,都将a[i]放到此防御系统中
        g[k] = a[i];
    }
    //输出大吉
    printf("%d\n", cnt);
    return 0;
}

4、二分法优化版本

#include 

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;
}