导弹拦截做题报告

为了这道题我大概搞了6小时左右,现在终于AC了。

题意

这道题就是要求一个数列中的最长不上升子序列最长上升子序列

至于第二问为什么是求最长上升子序列是因为:每一套导弹拦截系统数列的性质都是保证单调下降的的,所以每一个严格上升的数就是一个导弹拦截系统数列的开头。

题解

$2019.2.2$

关于实现有好多种做法。但是搞了半天我目前只理解了线段树做法

首先要先清楚暴力

for(int i=1; i<=n; i++) {//以求最长不上升子序列为例
    for(int j=1; j<i; j++) {
        if(a[j]>=a[i] && f[j]+1>f[i]) {
            f[i]=f[j]+1;
        }
    }
}

这个做法在显然是$O(n^2)$的,在这题会TLE

有没有优化呢?

我们发现其实可以利用线段树维护区间极值来省略一个循环,即寻找最大的$f[j] (a[j]>=a[i])$。

实现:

  • 当前导弹的高度的值做线段树的结点编号,线段树维护当前导弹的高度所对应的最大$f[i]$。
  • 以求最长不上升子序列为例,通过结点编号是当前导弹高度的条件,我们可以把寻找最大的$f[j] (a[j]>=a[i])$这个任务转化为$querymax(a[i]~max(a[j])(1<=j<=n)) ;$。而每次求完之后自然要更新一下$f[i]$的值。

$2019.2.3$

今天下午打算用再重温一遍这道毒瘤题。然后一直TLE,上面那个做法能过可能是我上次交的时候人品比较好吧。于是我打算用树状数组实现。然后我就完全照搬了线段树实现的思路。$XJB$打了一个貌似树状数组的东西。然后就$AC$了?

至于为什么不会TLE因为树状数组的更新和查询在常数上都是比线段树小的。而我昨天的时候还有个疑惑:你那既然这样还要线段树作甚?其实是因为线段树的应用范围比树状数组更广。比如说维护区间极值。线段树能做到,而树状数组不行。在这题树状数组充其量也就是维护了个前缀极值,即$[1,n]$的极值我当时还认为,哪像前缀和一样操作不久可以也变成区间极值了吗?很显然是不行的,因为区间极值不具有可减性

这道题让我对树状数组和线段树的理解更深了。

线段树基本上是万能的。而树状数组只能维护一些跟前缀和同样性质的信息。

毕竟是自己打的代码,大体思路我还是清楚的。但是毕竟我打完后是蒙蔽的。这里就搬出一篇较好的题解。

$2019.2.5$

现在是大年初一$2:40$分。时隔一年,我终于掌握了这道题的二分做法(滑稽)。

我们以求最长上升子序列为例:

  • 维护一个$d$数组,$d[i]$表示长度为$i$的最长上升子序列末尾的数的最小值。维护最小值是基于贪心的思想:显然一个最长上升子序列的末尾的数越小,其他的数接在该序列后面的可能性就越大。
  • 枚举每一个数。
    • 如果$x > d[len]$_(len为当前最长上升子序列的长度)_就让$x$接到当前最长上升子序列的下一位,并且将最长长度$+1$。
    • 如果$x <= d[len] $(len为当前最长上升子序列的长度) 就利用二分查找,用$x$来替换掉$d$数组中第一个大于等于$x$的数。来增加其他数接到后面的可能性。

以上就是二分做法的思路。

但是鸡肋的是,令我搞了一个多小时的不是这道题的做法,而是二分查找的写法。因为我实在太菜了,连二分都不会写。于是翻看了十几篇博客,总结出了我自己用的二分模板。

我们借一个不下降序列$a$举例:

  • 求这个序列中第一个大于等于的数的位置

    int l=0,r=n,ans=0,mid=0;
    while(l<=r) {
      mid=(l+r)>>1;
      if(a[mid] >= x) {//当找到了一个满足条件的答案
          r=mid-1;//因为:1.这个序列是递增的 2.我们要找的是第一个 3.当前找到的这个答案不一定是第一个 (这里写成l=mid+1不行是因为此时a[mid]已经>=x了,而这个序列又是递增的,所以在mid之后的数一定都>=x,一定不是第一个)
          ans=mid;//此时也可能是第一个,要及时更新答案。
      } else {
          l=mid+1;//当a[mid] <= x时,因为这个序列是递增的,所以就把范围改到[mid+1,r]。
      }
    }
    printf("%d",ans);

反思

大体思路也就这样。但是我DEBUG了两三个小时。

首先是Re:0

#include <cstdio>

#define lson (o<<1)
#define rson (o<<1|1)
#define inf 0x7fffffff
#define max(a,b) (a)>(b)?(a):(b)

const int N = 50010;
struct Segment_Tree {
    int maxv[N<<2],chav[N<<2];

    void pushup(int o) {
        maxv[o]=max(maxv[lson],maxv[rson]);
    }

     void bulid(int o,int l,int r) {
        if(l == r) {
            return ;
        }
        int mid=(l+r)>>1;
        bulid(lson,l,mid);bulid(rson,mid+1,r);
        pushup(o);
    }

     void change(int o,int l,int r,int q,int v) {
        if(l == r) {
            maxv[o]=v;
            //非常明显这个地方没有return;
        }
        int mid=(l+r)>>1;
        if(q <= mid) {
            change(lson,l,mid,q,v);
        } else {
            change(rson,mid+1,r,q,v);
        }
        pushup(o);
    }

   int querymax(int o,int l,int r,int ql,int qr) {
        if(ql<=l && r<=qr) {
            return maxv[o];
        }
        int mid=(l+r)>>1;
        int ans=0;
        if(ql <= mid) {
            ans=max(ans,querymax(lson,l,mid,ql,qr));
        }
        if(qr > mid) {
            ans=max(ans,querymax(rson,mid+1,r,ql,qr));
        }
        return ans;
    }
};

int ans=-inf,sum=-inf;
int s,n;
int a[100010];

int main(void) {
    Segment_Tree T,Q;
    while(scanf("%d",&a[++s])!=EOF) {
        n=max(n,a[s]);
    }
    s--;
    T.bulid(1,1,n);Q.bulid(1,1,n);
    for(int i=1; i<=s; i++) {
        int x=T.querymax(1,1,n,a[i],n)+1;
        T.change(1,1,n,a[i],x);
        ans=max(ans,x);
    }
    for(int i=1; i<=s; i++) {
        int x=Q.querymax(1,1,n,1,a[i]-1)+1;
        Q.change(1,1,n,a[i],x);
        sum=max(sum,x);
    }
    printf("%d\n%d",ans,sum);
}

总而言之以后打线段树的时候一定要放慢速度,而且好像每一个查询和修改操作都是要return的,检查时切记这点

线段树

#include <cstdio>

#define max(a,b) (a)>(b)?(a):(b)
#define lson (o<<1)
#define rson (o<<1|1)
#define inf 0x7fffffff

const int N = 50000;
struct Segment_Tree {
    int maxv[N<<2];

    void pushup(int o) {
        maxv[o]=max(maxv[lson],maxv[rson]);
    }

    void bulid(int o,int l,int r) {
        if(l == r) {
            return ;
        }
        int mid=(l+r)>>1;
        bulid(lson,l,mid);bulid(rson,mid+1,r);
        pushup(o);
    }

    void change(int o,int l,int r,int q,int v) {
        if(l == r) {
            maxv[o]=v;
            return ;
        }
        int mid=(l+r)>>1;
        if(q <= mid) {
            change(lson,l,mid,q,v);
        } else {
            change(rson,mid+1,r,q,v);
        }
        pushup(o);
    }

    int querymax(int o,int l,int r,int ql,int qr) {
        if(ql<=l && r<=qr) {
            return maxv[o];
        }
        int ans=0,mid=(l+r)>>1;
        if(ql <= mid) {
            ans=querymax(lson,l,mid,ql,qr);
        } 
        if(qr >  mid) {
            ans=max(querymax(rson,mid+1,r,ql,qr),ans);
        }
        return ans;
    }
}T,Q;

int ans=-inf,sum=-inf;
int s,n;
int a[100010];

int main(void) {
    while(scanf("%d",&a[++s])!=EOF) {
        n=max(n,a[s]);
    }
    s--;
    T.bulid(1,1,n);Q.bulid(1,1,n);
    for(int i=1; i<=s; i++) {
        int x=T.querymax(1,1,n,a[i],n)+1;
        ans=max(ans,x);
        T.change(1,1,n,a[i],x);
    }
    for(int i=1; i<=s; i++) {
        int x=Q.querymax(1,1,n,1,a[i]-1)+1;
        sum=max(sum,x);
        Q.change(1,1,n,a[i],x);
    }
    printf("%d\n%d",ans,sum);
}

树状数组

// luogu-judger-enable-o2
#include <cstdio>
#include <cstring>

#define max(a,b) (a)>(b)?(a):(b)

const int N = 100000 + 10;
int p;
int up_ans,unup_ans;
int a[N];
int maxv[N];
int n;

void change1(int o,int v) {
    while(o<=n) {
        maxv[o]=max(maxv[o],v);
        o+=o&(-o);
    }
}

int querymax1(int o) {
    int maxx=0;
    while(o) {
        maxx=max(maxx,maxv[o]);
        o-=o&(-o);
    }
    return maxx;
}

void change2(int o,int v) {
    while(o) {
        maxv[o]=max(maxv[o],v);
        o-=o&(-o);
    }
}

int querymax2(int o) {
    int maxx=0;
    while(o<=n) {
        maxx=max(maxx,maxv[o]);
        o+=o&(-o);
    }
    return maxx;
}

void Input() {
    while(scanf("%d",&a[++p]) != EOF) {
        n=max(n,a[p]);      
    }   
    p--;
} 

void Solve() {
    for(int i=1; i<=p; i++) {
        int x=querymax2(a[i])+1;
        change2(a[i],x);
        unup_ans=max(unup_ans,x); 
    }
    memset(maxv,0,sizeof(maxv));
    for(int i=1; i<=p; i++) {
        int x=querymax1(a[i]-1)+1;
        change1(a[i],x);
        up_ans=max(up_ans,x); 
    } 
    printf("%d\n%d",unup_ans,up_ans);
}

int main(void) {
    Input();
    Solve();
    return 0;
}

二分做法

#include <cstdio>

const int N = 100000+10;

int n;
int d1[N],d2[N];
int a[N];
int ans1=1,ans2=1;

int find1(int x) {
    int l=1,r=ans1,mid,ans;
    while(l <= r) {
        mid=(l+r)>>1;
        if(d1[mid] < x) {
            r=mid-1;
            ans=mid;
        } else {
            l=mid+1;
        }
    }
    return ans;
}

int find2(int x) {
    int l=1,r=ans2,mid,ans;
    while(l <= r) {
        mid=(l+r)>>1;
        if(d2[mid] >= x) {
            r=mid-1;
            ans=mid;
        } else {
            l=mid+1;
        }
    }
    return ans;
} 

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

void Solve() {
    d1[ans1]=d2[ans2]=a[ans1];
    for(int i=2; i<=n; i++) {
        if(a[i] <= d1[ans1]) {
            d1[++ans1]=a[i];
        } else {
            d1[find1(a[i])]=a[i];
        }
    }

    for(int i=2; i<=n; i++) {
        if(a[i] > d2[ans2]) {
            d2[++ans2]=a[i];
        } else {
            d2[find2(a[i])]=a[i];
        }
    }
    printf("%d\n%d",ans1,ans2);
}

int main(void) {
    Input();
    Solve();
    return 0;
}
posted @ 2019-02-02 07:56  加固文明幻景  阅读(7)  评论(0编辑  收藏  举报  来源