luogu P4198 楼房重建——线段树

题目大意:

小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。

施工队的建造总共进行了M天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi的房屋的高度变为Yi(高度可以比原来大—修建,也可以比原来小—拆除,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?

分析:

显然可以想到进行斜率处理,通过斜率的单调递增来求出len。

其实答案就是整个1—n区间中从第一项开始,每一个大于前一项的必选,小于等于前一项的必须不选,所的得到的序列长度。

因为区间是固定的,并且发现一个区间内的答案可以通过两个子区间用某种方式进行转移。所以可以考虑到线段树做法。

线段树中只需要维护两个值,一个是区间最大值,还有一个是区间序列长度(按照刚才的理解)的值。

建树(甚至不用),修改,甚至不用pushdown,一切好说。但是发现pushup不好处理,显然两个子区间的值不能直接合并。必须满足一定关系。

可以发现,区间内的第一项一定在这个序列内,区间最大值也一定在这个序列内。

对于要被pushup的区间,它的两个子区间已经处理好了,容易知道,左儿子区间内的序列每一项一定都在这个大区间内。(因为前面形态固定,又不能选择不看到)所以只需要处理右儿子区间和左儿子区间最大值的关系,即可递归处理len值。

递归要传入该区间的值必须大于的最小值,设为lx。对于开始进入时,也就是左儿子的最大值。

1.如果l==r,该位置的值大于lx,return1,否则return0;

2.将该区间劈成两段,设为s1,s2区间。

①如果s1的最大值小于等于lx,那么s1必然不会对答案产生贡献,去找s2。即代码中: return pushup2(lx,s2,mid+1,r)

②如果s1的最大值大于lx,那么s2中剩下的在s1,s2组成的原区间中做贡献的项一定能贡献到最终答案中。即+l(x)-l(s1),这里注意,不是 l(s2),因为可能在l(s2)中存在的项,不一定在l(x)这个大区间中出现。所以这两个值是完全不同的概念。

之后再去寻找s1. 即:return pushup2(lx,s1,l,mid)+l(x)-l(s1);

核心代码:

int pushup2(double lx,int x,int l,int r)
{
    if(m(x)<=lx) return 0;//剪枝
    if(a[l]>lx) return l(x);//剪枝
    if(l==r) return a[l]>lx;//
    int s1=x<<1,s2=x<<1|1;
    int mid=(l+r)>>1;
    if(m(s1)<=lx) return pushup2(lx,s2,mid+1,r);//
    else return pushup2(lx,s1,l,mid)+l(x)-l(s1);//
}

详见代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100000+10;
int n,m;
double a[N];
struct node{
    double mx;
    int len;
    #define m(x) t[x].mx
    #define l(x) t[x].len
}t[4*N];
void pushup1(int x)
{
    m(x)=max(m(x<<1),m(x<<1|1));
}
int pushup2(double lx,int x,int l,int r)
{
    if(m(x)<=lx) return 0;
    if(a[l]>lx) return l(x); 
    if(l==r) return a[l]>lx;
    int s1=x<<1,s2=x<<1|1;
    int mid=(l+r)>>1;
    if(m(s1)<=lx) return pushup2(lx,s2,mid+1,r);
    else return pushup2(lx,s1,l,mid)+l(x)-l(s1);
}
void chan(int x,int l,int r,int to,int c)
{
    if(l==r&&l==to)
    {
        m(x)=(double)c/to;
        l(x)=1;
        return ;
    }
    int mid=(l+r)>>1;
    if(to<=mid) chan(x<<1,l,mid,to,c);
    else if(to>mid) chan(x<<1|1,mid+1,r,to,c);
    pushup1(x);
    l(x)=l(x<<1)+pushup2(m(x<<1),x<<1|1,mid+1,r);
}
int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        a[x]=(double)y/x;
        chan(1,1,n,x,y);
        printf("%d\n",t[1].len);
    }
    return 0;
}

总结:

1.其实这个题就是把pushup logn化,是pushup一种难度升级版。

2.只要可以想办法区间合并的问题,都可以尝试用线段树解决。虽然有时候一看看不出来。

猜你喜欢

转载自www.cnblogs.com/Miracevin/p/9031636.html