HDU 4967 Handling the Past 线段树 调用顺序

题意:对一个栈,有三种操作:pop,push,peak(查询当前栈顶元素的值)。但在此题中,每个操作都有时间戳。操作按顺序给出,但如果后面的操作的时间戳小,会把之前,大于该时间戳的所有操作全部取消,即恢复到该时间戳时的状态,执行完该操作后,再把取消的操作重新执行。

但,peak操作不会重新执行,即,对于给定的peak操作,它也会把大于它的时间戳的操作取消,完成自己的查询操作。但,对于后来的小于该时间戳的操作,不会重新执行这个peak操作。(可以认为,这个打印操作只会执行一次。)

思路:只是简单的按照时间排序是行不通的。因为peak操作只执行一次,它对前后操作的依赖关系很大,只按时间排序后,会把依赖关系的信息破坏掉。我们只能按照给定的操作顺序去处理。

我们可以利用线段树去解决这道题。

首先,因为时间的范围很大,而总的操作数很少,我们首先离散化时间。

然后:对于每个push操作,我们对对应的时间点+1,pop操作,我们对对应的时间点-1;

而peak操作,设对应的时间点为t,在这种情况下,就等价于,找出一个最大的时间点t',使 [ t', t)区间的和大于0(大于0保证栈不为空,最大的时间点保证是在当前时间点下,栈顶的元素。)

那,如何去求出这个最大的时间点t'呢?或者说,线段树该保存怎样的信息?

每个节点对应区间的区间和,这个是一定要有的。但我们如果只保存区间和,会发现,如果多次求不同区间的和,实际上会大大增加复杂度。

因为我们要求的是区间[t',t)的起点位置,而这个区间和,也相当于以t'为起点的后缀和,所以我们还要维护每个节点对应区间的最大后缀和。

最大后缀和的维护公式:

设节点为o,左儿子为ls,右儿子为rs,区间和为sum,最大后缀和为rsum,则 rum[o] = max(rsum[r], rsum[l] + sum[r]),即最大后缀和的起点要么位于右儿子对应区间,要么位于左儿子对应区间。

我们再回头来看如何求出t'。

我们可以利用线段树,将[1,t)分成log(t)个不相交的区间(分的方法就是递归调用,直到完全覆盖),从右到左,每个区间分开考虑,同时有一个变量v保存考虑过的区间的和。对于每个区间o:

1.如果v+ rsum[0] <= 0, 说明在这个区间内,无法找到t',那么 累加v = v + sum[o],继续向左考虑。

2.如果v + rsum[o] > 0,说明在这个区间内,存在某个时间点t',使[t',t)区间和大于0。由步骤1可知,这个区间右面的所有区间都不满足,而该区间满足,所以t'一定是最大的。

   然后递归考虑这个区间,直到找到准确的时间点为止。

3.如果已经到了最左端仍然没满足要求,说明不存在。

这样,处理这个题的整体思路就很清晰了。

代码如下:

#include 
#include 
#include 

using namespace std;

struct info{
    int s;
    int x;
    int t;
};

const int MAX = 50010;
int N,x,t;
char str[20];
info a[MAX];
int b[MAX];
int ans;


struct interval{
    int left, right;
    int sum, rsum;
};

struct SegmentTree{
    static const int MAX = 50010;
    interval node[MAX<<3];
    #define ls(o) (o<<1)
    #define rs(o) (o<<1|1)
    void build(int l, int r, int o){
        node[o].left = l;
        node[o].right = r;
        node[o].sum = node[o].rsum = 0;

        if(l == r) return;

        int mid = (l + r)>>1;
        build(l,mid,ls(o));
        build(mid+1,r,rs(o));
    }
    void pushup(int o){
        node[o].sum = node[rs(o)].sum + node[ls(o)].sum;
        node[o].rsum = max(node[rs(o)].rsum,node[ls(o)].rsum + node[rs(o)].sum);
    }

    void update(int value,int pos,int o){
        if(node[o].left == node[o].right &&
           node[o].left == pos){
            node[o].sum = node[o].rsum = value;
            return ;
           }
        int mid = (node[o].left + node[o].right) >> 1;
        if(pos <= mid) update(value,pos,ls(o));
        else update(value,pos,rs(o));

        pushup(o);
    }
    void getans(int v,int o){
        if(node[o].left == node[o].right){
            ans = b[node[o].left];
            //printf("%d ",node[o].left);
            return;
        }
        if(v + node[rs(o)].rsum > 0)
            getans(v,rs(o));
        else
            getans(v + node[rs(o)].sum,ls(o));
    }
    void query(int &v,int L, int R, int o){
        if(~ans) return;
        if(L <= node[o].left && R >= node[o].right){
            if(v + node[o].rsum <= 0) v += node[o].sum;
            else getans(v,o);
            return;
        }
        int mid = (node[o].left + node[o].right) >> 1;

        if(R > mid) query(v,L,R,rs(o));
        if(L <= mid) query(v,L,R,ls(o));
    }

};
SegmentTree tree;


int main(void)
{
    int cas = 1;
    //freopen("input.txt","r",stdin);
    while(scanf("%d", &N),N){
        for(int i = 0 ; i < N; ++i){
            scanf("%s",str);
            if(str[1] == 'u'){
                a[i].s = 1;
                scanf("%d", &a[i].x);
            }
            else if(str[1] == 'o')
                a[i].s = 2;
            else
                a[i].s = 3;
            scanf("%d", &a[i].t);
            b[i] = a[i].t;
        }

        sort(b,b + N);
        for(int i = 0 ; i < N; ++i)
            a[i].t = lower_bound(b, b + N, a[i].t) - b + 1;
        memset(b,0,sizeof(b));
        for(int i = 0 ; i < N; ++i)
            if(a[i].s == 1)
                b[a[i].t] = a[i].x;
       // for(int i = 1; i <= N; ++i)
        //    printf("%d%c",b[i],i == N?'\n':' ');

        printf("Case #%d:\n",cas++);
        tree.build(1,N,1);
        for(int i = 0 ; i < N; ++i){
            if(a[i].s == 1)
                tree.update(1,a[i].t,1);
            else if(a[i].s == 2)
                tree.update(-1,a[i].t,1);
            else{
                int v =0;
                ans = -1;
                tree.query(v,1,a[i].t,1);
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}

好的姿势:

1.利用引用,使递归调用查询的时候,能对考虑过的节点累加和。

2.递归调用的顺序决定了搜索的方向。在这个题中,我们要先求右子树,在求左子树,才能保证是从右向左的查找。

3.对于答案,我们可以直接初始化为不存在的答案,在输出结果时,不需要判断。

4.-1的妙用,~-1 = 0,代码中,利用~ans结束求解过程,妙!

你可能感兴趣的:(数据结构-线段树,学姿势)