题解 lg4198 楼房重建--线段树
题意
求一个带修改序列的最大单调上升子序列的长度
题外话
我最开始以为只是一座楼房比另一个楼房高就可以看到,但实际上这是无比错误的.
在图上画画图就可以明白
序列里面是斜率!!!
思路
带了修改,有区间,考虑用线段树来维护
对于一个区间 \(x\) ,我们维护 \(len\) 和 \(maxn\) 分别代表从这个区间开头的最大单调上升子序列长度 和 区间最大值
维护\(maxn\)很简单,所以着重考虑的是\(len\)
对于一个区间 \(x\) ,设其左右区间为 \(lc(x)\) , \(rc(x)\),左区间的 \(len\) 显然可以作为\(len[x]\)的答案组成,接着考虑右区间,若 \(maxn[lc(x)]\leq maxn[rc(x)]\),右区间就没有贡献了
否则对右区间继续考虑,只有 \(maxn[lc(rc(x))]> maxn[lc(x)]\) 右区间的左子区间才会有贡献,此时右区间的右子区间只有在\(maxn[rc(rc(x))]>maxn[lc(rc(x))]\)才会有贡献;当\(maxn[lc(rc(x))]\leq maxn[lc(x)]\),此时右区间的右子区间只有在\(maxn[rc(rc(x))]>maxn[lc(x)]\)才会有贡献,这样可以递归下去直到单点,最后统计贡献.以上稍微想想模拟一下就可以明白了.
但有个问题,对于只有 \(maxn[lc(rc(x))]> maxn[lc(x)]\) 右区间的左子区间才会有贡献,此时右区间的右子区间只有在\(maxn[rc(rc(x))]>maxn[lc(rc(x))]\)才会有贡献这种情况,你会发现两边都要递归,那么上限会是\(O(n)\)!!!.不过不急,思考一下\(len\)的意义,你会发现右区间的右子区间的贡献可以通过\(len[rc(x)]-len[lc(rc(x))]\)算出不是
查询就直接在根节点上查,这样我们就得到了一个\(O(nlog_{2}^{2}n)\)的优秀算法
代码
#include<bits/stdc++.h>
using namespace std;
int const MAXN=1e5+10;
int n,m;
struct SegmentTree{
#define lc(x) x<<1
#define rc(x) x<<1|1
int len[MAXN<<2],l[MAXN<<2],r[MAXN<<2];
double maxn[MAXN<<3];
void build(int x,int L,int R){
l[x]=L,r[x]=R;
if(L==R)return;
int mid=(L+R)>>1;
build(lc(x),L,mid);
build(rc(x),mid+1,R);
}
int find(int x,double h){
if(l[x]==r[x]){
if(maxn[x]>h)return 1;
return 0;
}
int sum=0;
if(maxn[lc(x)]>h){
sum+=find(lc(x),h)+len[x]-len[lc(x)];
}else if(maxn[rc(x)]>h){
sum+=find(rc(x),h);
}
return sum;
}
void pushup(int x){
maxn[x]=max(maxn[lc(x)],maxn[rc(x)]);
len[x]=len[lc(x)];
if(maxn[rc(x)]>maxn[lc(x)]){
len[x]+=find(rc(x),maxn[lc(x)]);
}
return;
}
void insert(int x,int L,int R,int p,int a){
l[x]=L,r[x]=R;
if(l[x]==r[x]){maxn[x]=(double)a/(double)p,len[x]=1;return;}
int mid=(L+R)>>1;
if(p<=mid)insert(x<<1,L,mid,p,a);
else insert(x<<1|1,mid+1,R,p,a);
pushup(x);
}
}Tree;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
Tree.insert(1,1,n,x,y);
printf("%d\n",Tree.len[1]);
}
return 0;
}