luoguP1020 导弹拦截

限于喜欢 提交于 2019-12-03 14:45:46

题意

题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是\le 50000≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式
11行,若干个整数(个数\le 100000≤100000)

输出格式
22行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入输出样例
输入 #1
389 207 155 300 299 170 158 65
输出 #1
6
2
说明/提示
为了让大家更好地测试n方算法,本题开启spj,n方100分,nlogn200分

每点两问,按问给分

分析

LIS的两种优化(好像不用这个做也行....但我不会呀)

问题1: 显然,是求最长不上升子序列

问题2: 分析一下, 问需要多少个系统才能把导弹全部拦截。 一个系统所能拦截的导弹是“不上升的”, 所以我们手算一下样例, 发现有转折的地方(“155”->“300”), 是必须要多加一个系统的。 而“207”->“300”其实也是一个转折, 但上一个加的系统就能够拦截到“207”了, 所以,我们再多造几组数据,发现,这个就是一个最长上升子序列。(其实我也不会...)

这里主要讲讲它的优化, 我们看见题目明确的要nlogn算法, LIS的nlogn算法(我知道的)有树状数组优化和二分+贪心+类似栈的东西

树状数组优化: 因为它只添加,而且t数组也是只增不减的, 所以可以用树状数组做。 注意实现的细节: 求以x结尾的最长上升子序列的时候, 不能把x算上, 因为可能在之前出现过x。

#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
#define lowbit(x) (x&-x)
const int MAX = 50000+9;
const int N = 100000+9;

int ft[MAX], mx, num;
int a[N];

void add1(int x, int k) {while(x <= MAX) ft[x] = max(ft[x], k), x += lowbit(x);}
int query1(int x) {
    int mx = -1;
    x -= 1;//LIS:是上升! 所以不包括相等 
    while(x) {
        mx = max(ft[x], mx);
        x -= lowbit(x);
    }
    return mx;
}

void add2(int x, int k) {while(x) ft[x] = max(ft[x], k), x -= lowbit(x);}
int query2(int x) {//最长不上升序列: 可以相等 
    int mx = -1;
    while(x <= MAX) {
        mx = max(ft[x], mx);
        x += lowbit(x);
    }
    return mx;
}

int main() {
    int n = 0;
    while(scanf("%d", &a[++n]) == 1) ;
    --n;//多读了一个 
    
    mx = 1;
    for(int i = 1; i <= n; i++) {
        ft[a[i]] = query2(a[i]) + 1;
        mx = max(ft[a[i]], mx);
        add2(a[i], ft[a[i]]);
    }
    printf("%d\n", mx);
    
    memset(ft, 0, sizeof(ft));
    
    num = 0;
    for(int i = 1; i <= n; i++) {//求LIS 
        ft[a[i]] = query1(a[i]) + 1;
        num = max(num, ft[a[i]]);
        add1(a[i], ft[a[i]]);
//      printf("%d ", ft[a[i]]);
    }   
    printf("%d", num);
    return 0;
}

二分+贪心(这里只给出LIS的二分代码)
其实这个我也不知道为什么能A,有可能是因为我看它的代码的时间比较长, 背下来了吧(不然怎么自己打出来了呢?

定义f[i]: 长度为i的上升子序列的末尾元素的最小值, 贪心的思路, f[i]越小, 我们后面的选择就越多。显然, f是递增的(自己写下数据)

f[1] = a[1], len = 1,(len表示当前的最长长度)
接下来i从2开始向后枚举 如果a[i]>f[len] f[++len]=i;//放到后面 不然每次在f里面二分查找第一个大于等于a[i]的数,下标为l
(因为要是f[mid]<a[i], 那么显然f[mid] 包括 f[mid-1]及以前的都不能更新, 所以l = mid+1。 如果f[mid] >= a[i], 那么l~mid是可能需要更新的,而mid右边的f[]>a[i], 不能用a[i]更新f[], 所以r = mid) (注: 左闭右开
最后比较大小,f[l]=min(f[l],a[i])

int len = 1;
ft[1] = a[1];
for(int i = 2; i <= n; i++) {//求LIS 
    if(a[i] > ft[len]) ft[++len] = a[i];
    else {
        int l = 0, r = len, mid;
        while(l < r) {
            mid = (l+r)>>1;
            if(ft[mid] >= a[i]) r = mid;
            else l = mid+1;
        }
        ft[l] = min(ft[l], a[i]);
    }
}   
printf("%d", len);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!