[BZOJ2957]楼房重建(线段树)

博客详细解析了[BZOJ2957]楼房重建问题的解题思路,利用线段树维护上升序列长度。在动态修改点值的情况下,通过线段树更新并查询最长上升序列。关键在于理解在update过程中,右子树需考虑左子树最大值的影响,导致'左子树+右子树'不等于总和。
摘要由CSDN通过智能技术生成

=== ===

这里放传送门

=== ===

题解

可以发现如果一段楼房能被看见,那么它们跟原点的连线的斜率都是单调递增的。于是这就变成了一个维护上升序列的题。这里的上升序列不是最长上升子序列那样的东西,而是相当于贪心地选择,选中的子序列中的每一个元素它前面都不能存在大于等于它的元素。比如说,有一个斜率序列是1,2,4,3,4,如果是做最长上升子序列那种东西的话我们会选择[1,2,3,4]这个子序列,但这道题里面不行,因为第四个位置的3被第三个位置的4给挡住了,就看不见了,所以只能选到[1,2,4]这个子序列。

那么问题就变成了动态修改某个点的值,求整个序列里最长的这样的上升序列长度,可以用线段树来维护。首先因为这个东西和区间最大值有关,如果某个区间的最大值比较大的话它可能会挡住后面所有的区间,后面的区间就不会有贡献了。所以维护一个Max表示区间最大值。然后再维护一个val表示当前区间里最长的上升序列的长度。注意这里是仅仅考虑当前区间不考虑左右两边是什么情况,这样才好用线段树递归更新求解。

修改最大值好办,关键是如何维护val。每次输出的答案其实就是val[1],那么就考虑如何在修改以后把val给正确地update上去。在线段树中,一个区间被mid分成了[l..mid]和[mid+1..r]两个部分,显然这个区间的val至少是它左子树的那一部分,要看见右子树上的那些房子就肯定也能看见左子树最长的那一部分。那么就是如何计算右子树那一部分了。不能直接累加val[(i<<1)+1],因为前面提到过线段树的每个节点里面维护的长度信息都是不考虑左右两边的情况的,但是在update的时候,右子树必须要考虑左子树的情况。有可能左子树的Max比较大然后右子树的上升序列就被截掉了一部分,不能全部被看见。所以要继续调用过程在线段树里面查询。

调用一个类似二分的divide过程,带入一个比较参数v表示这段区间里要查找的,最小值必须大于v的一段上升序列的长度。在update的时候要对右子树节点调用这个过程,带入的比较参数就是左子树的最大值。显然右子树能看见的那一段上升序列的开头必须至少要大于左子树最大值对吧。然后就进去查询。注意这个divide过程的用途就是查询在当前区间里所有大于等于v的数字中的最长上升序列。那么如果当前区间的最大值直接就小于等于v就直接返回0,如果当前区间只有一个数字的话就把它跟v比较一下返回结果。否则还是要把这个区间分成左右子树的两个部分来讨论。如果左子树的最大值小于等于v,那么显然答案只会有右子树的那一部分,就对右子树接着调用divide即可;否则可以看出右子树那一部分会全部被看见,只需要对左子树调用divide即可。而这里最重要的一个问题就是计算的“右子树那一部分”并不是val[(i<<1)+1],同样是因为右子树那块没考虑到左子树的影响,这作为一个整体的时候应该是要考虑左子树的。因为要返回的是这段区间的上升序列长度,所以右子树给的贡献应该是总的减去左子树的。这跟val[(i<<1)+1]不一样,不一样,真的不一样。。。。。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,val[500010];
double Max[500010];
bool dlt[500010];
int divide(int i,int l,int r,double v){
    if (l==r) return Max[i]>v;
    int mid=(l+r)>>1;
    if (Max[i]<=v) return 0;
    if (Max[i<<1]<=v) return divide((i<<1)+1,mid+1,r,v);
    else return val[i]-val[i<<1]+divide(i<<1,l,mid,v);
}
void change(int i,int l,int r,int x,double v){
    if (l==r){
        Max[i]=v;val[i]=1;
        return;
    }
    int mid=(l+r)>>1;
    if (x<=mid) change(i<<1,l,mid,x,v);
    else change((i<<1)+1,mid+1,r,x,v);
    Max[i]=max(Max[i<<1],Max[(i<<1)+1]);
    val[i]=val[i<<1]+divide((i<<1)+1,mid+1,r,Max[i<<1]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        double v=(double)y/(double)x;
        change(1,1,n,x,v);
        printf("%d\n",val[1]);
    }
    return 0;
}

偏偏在最后出现的补充说明

这题最难绕的就是“左子树+右子树”并不等于总的,因为它update的时候右子树是特殊计算的。还是因为在这个最长上升序列的问题中虽然计算单个区间的时候是不考虑左右影响的,但是update的时候为了正确得到这个区间的结果,右子树还是要考虑左子树的影响的,这是造成val[i<<1]+val[(i<<1)+1] val[i]的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值