Codeforces 739C.Alyona and towers (线段树/非递归线段树)

题目链接:http://codeforces.com/contest/739/problem/C

题意:给定30万个正整数,和30万个操作,每次操作后输出查询结果。

操作:每次把从L到R的数都加上D

查询:最宽的Hill的宽度。定义:先递增后递减的连续n个数称作宽度为n的Hill。


方法一:递归线段树(类似最长连续零的思路)1965ms/2000ms

这是最开始的思路,先定义Hill结构体,来存一个Hill的信息。

然后再定义Segment为线段树元素,其中包含3个Hill,从最左侧元素开始的最宽Hill,整个线段的最宽Hill,和最右侧线段的最宽Hill。


更新区间信息:

如果左子区间整个是一个Hill(代码中用pure表示),那么合并后的区间的左Hill=左子区间的左Hill 与 右子区间的左Hill合并。

这个合并是函数CLeft实现的,因为这个合并是基于左Hill的,计算出来的区间一定包含左Hill。

右子区间同理。合并后整个的最宽Hill = 左子区间最宽Hill ,右子区间最宽Hill 以及中间可能生成的新Hill中取最大值。

中间可能生成的新Hill是重载的Hill的加法。


总结:

这个算法不够简洁,Hill的加法居然写了3种。而且写到最后是卡时间过的。1965ms/2000ms 

并且这是区间修改的线段树,带懒惰标记的。没办法写成非递归来加速。


方法二:差分+非递归线段树   389 ms/2000ms

看了官方题解和一些别人代码,发现别人的线段树是每个节点3个数的点修改线段树,而我写的是每个节点18个数的区间修改线段树。

首先是差分,假设原数组为A,则a[ i ] = A[ i + 1 ] - A[ i ] ,然后将正数记为1,负数记为-1.

差分的作用就是化区间修改为点修改,然后就可以愉快的用非递归优化了。

于是,题目转化为,从一个1,0,-1三个值组成的数列中,找到最长的(连续1后跟连续-1)的序列,以下简称序列。

注意这里的长度变化: Hill宽度 = 序列长度 + 1


先讲递归的:

每个节点存3个数,从左开始的最长序列的长度,整个区间最长序列的长度,从右开始的最长序列长度。

合并时,比较左区间的最右值和右区间的最左值。

当以下3个条件任何一个满足时,中间不会生成新序列,否则会生成新序列。

1.左区间最右值为0

2.右区间最左值为0

3.左区间最右值为-1 且 右区间最左值为1

不生成新序列时,合并很简单。

生成新序列时,要考虑左子区间是否是整个都是一个序列,才能判断如何计算合并区间的从左开始的序列。右子区间同理。

然后中间生成的新序列长度直接求和就行。


然后是非递归:

递归时只用记录3个长度,是因为区间的边界已经包含在线段树的函数的参数里了。

不用记录值,是因为可以通过下标直接访问a数组(这点在区间修改的线段树中是做不到的,所以方法一中必须存边界点对应的值)

而从递归到非递归,最简单的想法是,非递归线段树中,每个节点存它的左右边界,然后就跟递归的一样了。

不过我存的是,中间的界限的下标和该区间的长度。这样也可以,因为区间合并时,其实只需要求M=(L+R)/2.

于是我就直接记录M值,而不需要记录左右边界再来计算M,另一个用到左右边界的地方是判断是否整个区间都是一个序列,这时实际上是用的R-L+1.

于是我就直接记录区间长度Len。相当于省了一点点计算。

M和Len是在Build时计算,然后之后都不会改变了。

然后是前面提到的三个长度lv,mv,rv。

于是每个节点5个值。


要注意的一点是,递归时,a数组的大小只需要开到n-1(n为数字总数)就行了。

非递归时必须开到大于等于n+1(注意是加号)的最小的2的次方,因为在非递归线段树中直接访问了a数组,而非递归线段树是扩充了原数组数量的。


最后附上代码:

递归代码:

/*
2016.12.4: Segment Tree AC   1965ms/2000ms

*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#define maxn 300001
#define LL long long
#define lson l,m,rt<<1 
#define rson m+1,r,rt<<1|1
using namespace std;

struct Hill{
    int Lid,Mid,Rid;//[L,R]
    LL Lv,Mv,Rv;
    bool valid;
    Hill():valid(false){}
    Hill(int Lid,int Mid,int Rid,LL Lv,LL Mv,LL Rv):Lid(Lid),Mid(Mid),Rid(Rid),Lv(Lv),Mv(Mv),Rv(Rv),valid(true){}
    int Len()const{return valid ? Rid - Lid + 1 : 0 ;}
    Hill operator +(const Hill B)const{
        if(Rv == B.Lv) return Hill();//no new hill
        if(Rv < B.Lv){
            if(Mid == Rid)  return Hill(Lid,B.Mid,B.Rid,Lv,B.Mv,B.Rv);
            else            return Hill(Rid,B.Mid,B.Rid,Rv,B.Mv,B.Rv);
        }
        else{
            if(B.Mid == B.Lid)  return Hill(Lid,Mid,B.Rid,Lv,Mv,B.Rv);
            else                return Hill(Lid,Mid,B.Lid,Lv,Mv,B.Lv);
        }
    }
    bool operator < (const Hill B)const{return Len() < B.Len();}
};
Hill CLeft(const Hill A,const Hill B){//Combine A and B based on A
    if(!B.valid || B.Lv == A.Rv) return A;
    if(A.Rv > B.Lv){
        if(B.Lid == B.Mid) return Hill(A.Lid,A.Mid,B.Rid,A.Lv,A.Mv,B.Rv);
        else               return Hill(A.Lid,A.Mid,B.Lid,A.Lv,A.Mv,B.Lv);
    }
    else{
        if(A.Mid == A.Rid) return Hill(A.Lid,B.Mid,B.Rid,A.Lv,B.Mv,B.Rv);
        else return A;
    }
}
Hill CRight(const Hill A,const Hill B){//Combine A and B based on B
    if(!A.valid || A.Rv == B.Lv) return B;
    if(B.Lv > A.Rv){
        if(A.Rid == A.Mid)  return Hill(A.Lid,B.Mid,B.Rid,A.Lv,B.Mv,B.Rv);
        else                return Hill(A.Rid,B.Mid,B.Rid,A.Rv,B.Mv,B.Rv);
    }
    else{
        if(B.Mid == B.Lid) return Hill(A.Lid,A.Mid,B.Rid,A.Lv,A.Mv,B.Rv);
        else return B;
    }
}
struct Segment{
    Hill L,M,R;
    bool pure;
    Segment(){}
    Segment(Hill L,Hill M,Hill R,bool pure):L(L),M(M),R(R),pure(pure){}
    Segment operator +(const Segment B)const{
        Segment Ans;
        
        Ans.L = pure ? CLeft(L,B.L) : L;
        Ans.R = B.pure ? CRight(R,B.R) : B.R;
        
        Hill CM = M  < B.M ? B.M : M ;
        Ans.M = R + B.L;
        if(Ans.M < CM) Ans.M = CM;
        Ans.pure = Ans.M.Len() == Ans.R.Rid - Ans.L.Lid + 1;
        return Ans;
    }
    void Add(const LL D){
        L.Lv += D;
        L.Mv += D;
        L.Rv += D;
        M.Lv += D;
        M.Mv += D;
        M.Rv += D;
        R.Lv += D;
        R.Mv += D;
        R.Rv += D;
    }
};

//Segment Tree
Segment S[maxn<<2];
LL Add[maxn<<2];
int mm[maxn<<2];
void PushUp(int rt){
    S[rt] = S[rt << 1] + S[rt << 1 | 1];
}
void PushDown(int rt){
    int ls = rt << 1 , rs = ls | 1;
    if(Add[rt]){
        Add[ls] += Add[rt];
        Add[rs] += Add[rt];
        S[ls].Add(Add[rt]);
        S[rs].Add(Add[rt]);
        Add[rt] = 0;
    }
}
void Build(int l,int r,int rt){
    if(l==r){
        int v;
        scanf("%d",&v);
        Hill tempH(l,l,l,v,v,v);
        S[rt] = Segment(tempH,tempH,tempH,true);
        return;
    }
    int m = mm[rt] = (l + r) >> 1;
    Build(lson);
    Build(rson);
    PushUp(rt);
    Add[rt] = 0;
}
void Update(const int L,const int R,const LL D,const int l,const int r,const int rt){
    if(L <= l && r <= R){
        Add[rt] += D;
        S[rt].Add(D);
        return;
    }
    const int m = mm[rt];
    PushDown(rt);
    if(L <= m) Update(L,R,D,lson);
    if(R >  m) Update(L,R,D,rson);
    PushUp(rt);
}

int main(){
    int n,m;
    while(~scanf("%d",&n)){
        Build(1,n,1);
        scanf("%d",&m);
        for(int i = 0 ; i < m ; ++ i){
            static int l,r,d;
            scanf("%d%d%d",&l,&r,&d);
            Update(l,r,d,1,n,1);
            printf("%d\n",S[1].M.Len());
        }
    }
    return 0;
}


非递归线段树:

/*
2016.12.7: 
Non-recursive Segment Tree AC 389 ms / 2000 ms
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#define LL long long
using namespace std;

struct Segment{
    int M,Len;
    int lv,mv,rv;//How long the sequence is : left,max,right
    bool pure()const{return mv == Len;}
    Segment(){}
    Segment(int M,int Len,int lv,int mv,int rv):M(M),Len(Len),lv(lv),mv(mv),rv(rv){}
};
inline int sign(LL x) {
    return x ? x > 0 ? : -1 : 0;
}

LL a[524288];//a[i]=A[i+1]-A[i]; starting from 1
//Segment Tree
int N;
Segment S[1048576];
void PushUp(int rt){
    //Triky part
    int ls = rt << 1 , rs = ls | 1;
    int M = S[rt].M;
    S[rt].mv = max(S[ls].mv,S[rs].mv);
    if(!a[M] || !a[M+1] || sign(a[M]) < sign(a[M+1])){
        S[rt].lv = S[ls].lv;
        S[rt].rv = S[rs].rv;
    }
    else{
        S[rt].lv = S[ls].pure() ? S[ls].lv + S[rs].lv : S[ls].lv;
        S[rt].rv = S[rs].pure() ? S[rs].rv + S[ls].rv : S[rs].rv;
        S[rt].mv = max(S[rt].mv,S[ls].rv+S[rs].lv);
    }
}
void Build(int n){//get values from sign(a[i]) where i <- [1..n]
    N = 1;
    while(N < n + 2) N <<= 1;
    //Fill Leaves
    for(int i = n + 1; i < N; ++i) a[i] = 0;
    for(int i = 0, j = N + i; i < N; ++i, ++j){
        int x = !!sign(a[i]);
        S[j] = Segment(i,1,x,x,x);
    }
    //Fill the rest
    for(int i = N; --i;) {
        S[i].M = (S[i<<1].M + S[i<<1|1].M)>>1;
        S[i].Len = S[i<<1].Len + S[i<<1|1].Len;
        PushUp(i);
    }
}
void Update(int L,int D){
    a[L] += D;
    if(sign(a[L]) == sign(a[L]-D)) return;
    else{
        int x = !!sign(a[L]),s = N + L;
        S[s] = Segment(L,1,x,x,x);
        while(s>>=1) PushUp(s);
    }
}

int main(){
    int n,m;
    while(~scanf("%d",&n)){
        int pre,cur;
        scanf("%d",&pre);
        for(int i = 1; i < n; ++i){
            scanf("%d",&cur);
            a[i] = cur - pre;
            pre = cur;
        }
        Build(n - 1);
        scanf("%d",&m);
        for(int i = 0 ; i < m ; ++ i){
            static int l,r,d;
            scanf("%d%d%d",&l,&r,&d);
            if(l ^ 1) Update(l-1,d);
            if(r ^ n) Update(r,-d);
            printf("%d\n",S[1].mv+1);
        }
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/u012891242/article/details/53503077