洛谷 [P1020] 导弹拦截 (N*logN)

首先此一眼就能看出来是一个非常基础的最长不下降子序列(LIS),其朴素的 N^2做法很简单,但如何将其优化成为N*logN?
我们不妨换一个思路,维护一个f数组,f[x]表示长度为x的LIS的最大的最后一个数字是f[x]。(为什么是最大的?可以应用贪心的思想,发现对于相同的x,f[x]越大其后可能扩展的情况就越多,即就越优)我们可以发现f数组单调递减(为什么?也可使用反证法证明,在此不赘述)对于决策单调性问题,一般使用二分法优化,这就是logN的来历。二分的边界条件一定要写对。
代码如下:

#include 
#include 
#include 
#include 
#include 
using namespace std;
int read(){
    int rv=0,fh=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fh=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        rv=(rv<<1)+(rv<<3)+c-'0';
        c=getchar();
    }
    return fh*rv;
}//快读
int num[100005],f[100005],q[100005];
int find1(int x){
    int l=1,r=f[0],m=0;
    while(l<=r){
        m=(l+r)>>1;
        if(f[m]>=x){
            l=m+1;
        }else{
            r=m-1;
        }
    }
    //if(l<=f[0]) return l;
    if(r>=1) return r;
    return 0;
}
int find2(int x){
    int l=1,r=f[0],m;
    while(l<=r){
        m=(l+r)>>1;
        if(f[m]>=x){
            r=m-1;
        }else {
            l=m+1;
        }
    }
    if(r>=1) return r;
    return 0;
}
int main(){
    freopen("in.txt","r",stdin);
    while(scanf("%d",&num[++num[0]])==1) ;
    num[0]--;//一定要减
    f[1]=num[1];f[0]++;
    for(int i=2;i<=num[0];i++){
        int pos=find1(num[i]); 
        if(pos){
            if(f[0]==pos) f[0]++;
            f[pos+1]=max(f[pos+1],num[i]);
            //cout<pos+1]<else {
            f[1]=max(f[1],num[i]);
        }
    }
    cout<0]<for(int i=1;i<=f[0];i++){
        f[i]=1000005;
    }
    f[0]=1;
    f[1]=num[1];
    for(int i=2;i<=num[0];i++){
        int pos=find2(num[i]);
        if(pos){
            if(f[0]==pos) f[0]++;
            f[pos+1]=min(f[pos+1],num[i]);
        }else {
            f[1]=min(f[1],num[i]);
        }
    }
    cout<0];
    fclose(stdin);
    return 0;
}

对于第二问,可使用dilworth定理,翻译成通俗易懂的语言就是:
对于一个序列,其最少的不降子序列划分=最长上升子序列长度,所以对于第二问输出最长上升子序列长度即可。

你可能感兴趣的:(noip,题解)