ZOJ 3943 Himalayas —— 线段树优化,有丶东西

This way

题意:

告诉你n座山的高度,第i座山被称为山峰当且仅当 1 < i < n ∩ H i − 1 < H i > H i + 1 1H_{i+1} 1<i<nHi1<Hi>Hi+1
每次都有一个操作:
让区间l~r的山每座山的高度加上A+(i-l)*B
问你每次操作完之后有多少山峰

题解:

首先看到区间加上等差数列就知道基本上就是用线段树维护前后差值。老套路了
那么区间更新的时候就更新三个区间的值:
L~L (+A)
L+1~R (+B)
R+1~R+1 (-A-(r-l)*B)
接下来就是怎么算山峰的个数了。
由于我写过的线段树优化都是需要单点更新然后向上push_up的时候进行计算。
但是这道题目是区间更新…怎么办
然后我们发现一个重要的性质:B>1
也就是在区间更新的时候,最多只有两个点(L,R+1)的相对差会变小,L+1~R的相对差是永远增加的。
然后我们又发现只有下面两种情况发生的时候,需要单点更新:
ZOJ 3943 Himalayas —— 线段树优化,有丶东西_第1张图片
于是我大胆猜测单点更新的次数不会超过4n次。
于是我们需要维护的东西就有:(线段树是按照差值建树)
pre:这个区间最左端的值
suf:这个区间最右端的值
sum:这个区间的山峰数
mi:这个区间的最小值
neg:这个区间小于等于0的最大值。
那么接下来要做的事情就狠显然了:
if((mi[root]<0&&neg[root]+v>=0)||(mi[root]<=0&&neg[root]+v>0))
表示两种情况
1.如果这个区间的最小值<0且有一个点会变成上面图中的第二种或者第三种情况
2.如果这个区间的最小值<=0且有一个点会变成上面图中的第三种情况
就向下更新。

虽然这个线段树优化很简单很显然,但是我在之前没有做到过区间更新强制改成单点更新的题目
所以在比赛的时候还是没有做出来
但是我所有的性质和操作都已经知道了
如果这点举一反三的能力都没有的话,那必然终究一无所成,希望以后引以为戒

#include
using namespace std;
#define ll long long
const int N=2e5+5;
ll a[N],pre[N*4],suf[N*4],f[N*4],mi[N*4],neg[N*4];
int sum[N*4];
void push_up(int root){
    sum[root]=sum[root<<1]+sum[root<<1|1]+(suf[root<<1]>0&&pre[root<<1|1]<0);
    suf[root]=suf[root<<1|1];
    pre[root]=pre[root<<1];
    mi[root]=min(mi[root<<1],mi[root<<1|1]);
    if(neg[root<<1]<=0&&neg[root<<1|1]<=0)
        neg[root]=max(neg[root<<1],neg[root<<1|1]);
    else
        neg[root]=min(neg[root<<1],neg[root<<1|1]);
}
void build(int l,int r,int root){
    f[root]=sum[root]=0;
    if(l==r){
        suf[root]=pre[root]=mi[root]=neg[root]=a[l]-a[l-1];
        return ;
    }
    int mid=l+r>>1;
    build(l,mid,root<<1);
    build(mid+1,r,root<<1|1);
    push_up(root);
}
void push_down(int root){
    if(!f[root])return ;
    pre[root<<1]+=f[root];
    pre[root<<1|1]+=f[root];
    suf[root<<1]+=f[root];
    suf[root<<1|1]+=f[root];
    f[root<<1]+=f[root];
    f[root<<1|1]+=f[root];
    mi[root<<1]+=f[root];
    mi[root<<1|1]+=f[root];
    neg[root<<1]+=f[root];
    neg[root<<1|1]+=f[root];
    f[root]=0;
}
void update(int l,int r,int root,int ql,int qr,ll v){

            push_down(root);
    if(l>=ql&&r<=qr){
        if(l==r){
            pre[root]+=v;
            suf[root]+=v;
            mi[root]+=v;
            neg[root]+=v;
        }
        else{
            if((mi[root]<0&&neg[root]+v>=0)||(mi[root]<=0&&neg[root]+v>0)){
                int mid=l+r>>1;
                update(l,mid,root<<1,ql,qr,v);
                update(mid+1,r,root<<1|1,ql,qr,v);
                push_up(root);
            }
            else{
                pre[root]+=v;
                suf[root]+=v;
                f[root]+=v;
                mi[root]+=v;
                neg[root]+=v;
            }
        }
        return ;
    }
    //push_down(root);
    int mid=l+r>>1;
    if(mid>=ql)
        update(l,mid,root<<1,ql,qr,v);
    if(mid<qr)
        update(mid+1,r,root<<1|1,ql,qr,v);
    push_up(root);
    return ;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        int l,r;
        ll A,B;
        if(n<=2){
            while(m--){
                scanf("%d%d%lld%lld",&l,&r,&A,&B);
                printf("0\n");
            }
            continue;
        }
        build(2,n,1);
        while(m--){
            scanf("%d%d%lld%lld",&l,&r,&A,&B);
            if(l>=2)
                update(2,n,1,l,l,A);
            if(l<r)
                update(2,n,1,l+1,r,B);
            if(r<n)
                update(2,n,1,r+1,r+1,-A-B*(r-l));
            printf("%d\n",sum[1]);
        }
    }
    return 0;
}

你可能感兴趣的:(想法,线段树)