为了这道题我大概搞了6小时左右,现在终于AC了。
这道题就是要求一个数列中的最长不上升子序列和最长上升子序列。
至于第二问为什么是求最长上升子序列是因为:每一套导弹拦截系统数列的性质都是保证单调下降的的,所以每一个严格上升的数就是一个导弹拦截系统数列的开头。
关于实现有好多种做法。但是搞了半天我目前只理解了线段树做法。
首先要先清楚暴力
这个做法在显然是的,在这题会TLE
有没有优化呢?
我们发现其实可以利用线段树维护区间极值来省略一个循环,即寻找最大的。
实现:
- 用
当前导弹的高度
的值做线段树的结点编号,线段树维护当前导弹的高度
所对应的最大。
- 以求最长不上升子序列为例,通过结点编号是
当前导弹高度
的条件,我们可以把寻找最大的这个任务转化为。而每次求完之后自然要更新一下的值。
今天下午打算用再重温一遍这道毒瘤题。然后一直TLE
,上面那个做法能过可能是我上次交的时候人品比较好吧。于是我打算用树状数组实现。然后我就完全照搬了线段树实现的思路。打了一个貌似树状数组的东西。然后就了?
至于为什么不会TLE
。因为树状数组的更新和查询在常数上都是比线段树小的。而我昨天的时候还有个疑惑:你那既然这样还要线段树作甚?其实是因为线段树的应用范围比树状数组更广。比如说维护区间极值。线段树能做到,而树状数组不行。在这题树状数组充其量也就是维护了个前缀极值,即的极值我当时还认为,哪像前缀和一样操作不久可以也变成区间极值了吗?很显然是不行的,因为区间极值不具有可减性。
这道题让我对树状数组和线段树的理解更深了。
线段树基本上是万能的。而树状数组只能维护一些跟前缀和同样性质的信息。
毕竟是自己打的代码,大体思路我还是清楚的。但是毕竟我打完后是蒙蔽的。这里就搬出一篇较好的题解。
现在是大年初一分。时隔一年,我终于掌握了这道题的二分做法(滑稽)。
我们以求最长上升子序列为例:
- 维护一个数组,表示长度为的最长上升子序列末尾的数的最小值。维护最小值是基于贪心的思想:显然一个最长上升子序列的末尾的数越小,其他的数接在该序列后面的可能性就越大。
- 枚举每一个数。
- 如果_(len为当前最长上升子序列的长度)_就让接到当前最长上升子序列的下一位,并且将最长长度。
- 如果(len为当前最长上升子序列的长度) 就利用二分查找,用来替换掉数组中第一个大于等于的数。来增加其他数接到后面的可能性。
以上就是二分做法的思路。
但是鸡肋的是,令我搞了一个多小时的不是这道题的做法,而是二分查找的写法。因为我实在太菜了,连二分都不会写。于是翻看了十几篇博客,总结出了我自己用的二分模板。
我们借一部个不下降序列举例:
大体思路也就这样。但是我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;
}
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);
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下