导弹拦截做题报告

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

1|0题意

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

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

2|0题解

1|02019.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(n2)的,在这题会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]的值。

1|02019.2.3

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

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

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

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

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

1|02019.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);

3|0反思

大体思路也就这样。但是我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的,检查时切记这点

1|0线段树

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

1|0树状数组

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

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

__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/17776986.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(8)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示