P4198-楼房重建【线段树】
正题
题目链接:https://www.luogu.com.cn/problem/P4198
题目大意
\(n\)条线,开始时第\(i\)条是\((i,0)\)的一个点。
每次有操作把第\(x\)条线变成\((x,0)\)到\((x,y)\)。然后求从\((0,0)\)能看到几条线。
解题思路
把线变成斜率的话就是对于每个点求一个往后比它大的第一个点然后一直跳来做了。
线段树的话主要是合并区间的时候比较麻烦,一个暴力的想法是直接维护每个区间的序列,然后合并的时候一个在另一个上面二分,但是这样合并的复杂度是\(O(len)\)的。
发现我们需要的只是在右区间的序列上二分而已,而右区间的序列是由它线段树上的子树得到,所以我们没有必要真正的存下来维护的序列,可以直接在右边的线段树上二分。
大概的操作就是右边分出来的左右两个区间,如果左边的区间最大值要比目前的值要小,那么左边区间没有贡献,直接到右边。否则那么这样跳一定会到达左边区间的最大值,然后就是递归求右边区间的答案,这部分答案我们已经处理完了,直接累加然后递归作左区间即可。
这样时间复杂度就是\(O(n\log^2 n)\)的了
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int n,m,len[N<<2];
double w[N<<2],a[N];
int PushUp(double val,int x,int L,int R){
if(w[x]<=val)return 0;
if(L==R)return a[L]>val;
int mid=(L+R)>>1;
if(w[x*2]<=val)return PushUp(val,x*2+1,mid+1,R);
return len[x]-len[x*2]+PushUp(val,x*2,L,mid);
}
void Change(int x,int L,int R,int pos,double val){
if(L==R){w[x]=a[L]=val;len[x]=1;return;}
int mid=(L+R)>>1;
if(pos<=mid)Change(x*2,L,mid,pos,val);
else Change(x*2+1,mid+1,R,pos,val);
w[x]=max(w[x*2],w[x*2+1]);
len[x]=len[x*2]+PushUp(w[x*2],x*2+1,mid+1,R);
}
int main()
{
scanf("%d%d",&n,&m);
while(m--){
int x,y;
scanf("%d%d",&x,&y);
Change(1,1,n,x,(double)y/x);
printf("%d\n",len[1]);
}
return 0;
}