[NOIP1999 普及组] 导弹拦截

题目传送门

分析

 1e5的数据,要nlogn才能过
 第一问求的是 最长不上升序列, 第二问求的是 最少的不上升子列个数

第一问:

传统的dp求LIS是 \(n^2\) 的复杂度,事实上第二层循环可以优化到logn
这里针对本题阐述一下口胡一下

对于第i个数x,如果x小于等于序列当前元素时,直接加入到序列中
如果x大于序列当前元素时,在序列中二分查找到小于x的最大值的位置,用x替换掉原来的值
 ps:可以注意到此时保存的序列很大可能就不是LIS序列,但是它的最终的长度一定是LIS长度
 这样子更新可以理解为既不破坏原来的最长序列的结构,同时也能使该序列具有更大的延续性(因为用了一个更大的x接替了原来小一点的数)
但是这样子最终只能得到LIS的长度,没法得到序列,目前还没有想出求序列的办法

第二问:

luogu题解中很多可以证明实际上就是求最长公共上升序列的,但是我不会证明
和第一问的logn处理有类似的联想,我想到的是对于每一个新加入的数x,应该找到各系统中当前高度大于等于x的最小值,把x接在后面
最优性解释是如果把x放在了另外一个系统上,那么下一个比x大一点的数就不一定能放到当前系统中
以上用数学表示就是,存在两个系统当前高度为a, a + 1;
此时新加入的一个数x = a,那么把x放在高度为a的系统肯定比放在高度为a + 1的系统更优,
因为如果下一个数x = a + 0.5,此时就没办法放到当前的系统中了。
过程可以用平衡树维护,STL中set还可以做到直接查询大于等于x的最小值的位置,见代码

代码

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
#define rg register
inline int read(){
    rg int x = 0, f = 1;
    rg char c = getchar();
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * f;
}
const int N = 1e5 + 1, max_h = 5e4 + 1;
int n, prein;
int a[N];
set<int> q;
int missile[N];

inline void init(){
    while (scanf("%d", &prein) != EOF)
        a[++n] = prein;
}

inline void doit(){
    int sum = 0;
    missile[0] = max_h;
    for (int i(1); i <= n; ++i){
        if (a[i] <= missile[sum]) missile[++sum] = a[i];
        else {
            int l = 1, r = sum;
            while (l < r){
                int mid = l + r >> 1;
                if (missile[mid] < a[i]) r = mid;
                else l = mid + 1; 
            }
            missile[l] = a[i];
        }
        set<int>::iterator it = q.lower_bound(a[i]);
        if (it != q.end()) 
            q.erase(it);
        q.insert(a[i]);
    }
    printf("%d\n%d", sum, q.size());
}

int main(){
    ios::sync_with_stdio(false);
    cout.tie(NULL);
    init();
    doit();
    return 0;
}
posted @ 2023-01-08 20:52  ancer  阅读(89)  评论(0)    收藏  举报