码迷,mamicode.com
首页 > 其他好文 > 详细

HDU - 1540 线段树的合并

时间:2019-02-01 23:29:49      阅读:187      评论:0      收藏:0      [点我收藏+]

标签:两种   names   节点   分离   int   端点   har   algorithm   现在   

  这个题题意我大概解释一下,就是一开始一条直线,上面的点全是联通的,有三种操作

1.操作D把从左往右第x个村庄摧毁,然后断开两边的联通。

2.询问Q节点相联通的最长长度

3.把最后破坏的村庄重建。

这个其实也是非常典型的线段树区间合并,正好可以学一下。

我们给线段树的结点赋予5个值,l 区间左端点, r 区间右端点, ls从左端点开始的最大连续个数(左连续),rs从右端点开始最大的连续个数,ms这个节点所表示的区间内部,最大的连续个数。

然后我们考虑建树,由于最开始是相通的,因此这些值初始为r-l+1

然后常规操作单点修改,现在考试如何维护的:

首先是左儿子节点的左连续传给父节点的左连续,右儿子的右连续传给父节点的右连续

然后是维护父节点的最大连续,父节点的最大连续有可能由三个部分得到:两个儿子节点的分离的最大连续,以及合并左儿子的右连续和右儿子的左连续。因此把这三个值的最大值拿出来即可。

然后考虑询问:

如果询问到叶节点,或者区间内部最大的连续区间为0(代表区间内全是0)或者区间值等于区间长度(区间满了)这些可以直接退出循环。

让后我们考虑询问分裂的时候的问题,如果我们询问的点在树的左边,我们会发现有两种情况:

如果这个点是大于在树的左儿子的右连续的左边界,意味着这个点联通着树的右节点,我们需要在询问左节点的同时,加上右节点的右连续,即t>=tree[L(root)].r-tree[L(root)].rs+1

否则我们只需要访问左节点,继续询问即可。

反之也是如此,从而实现了线段树的分裂和合并

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int maxx = 1e5+7;
int s[maxx];
inline int L(int r){return r<<1;};
inline int R(int r){return r<<1|1;};
inline int MID(int l,int r){return (l+r)>>1;};
struct node{
  int l,r;
  int ls,rs,ms;//左段开始最大连续区间,右端开始最大连续区间,节点区间内最大连续区间
}tree[maxx<<2];
void buildtree(int root,int l,int r){
   tree[root].l=l;
   tree[root].r=r;
   tree[root].ls=(r-l+1);
   tree[root].rs=(r-l+1);
   tree[root].ms=(r-l+1);
   if(l==r){
      return;
   }
   int mid=MID(l,r);
   buildtree(L(root),l,mid);
   buildtree(R(root),mid+1,r);
}
void update(int root,int t,int op){
   int l=tree[root].l;
   int r=tree[root].r;
   if(l==r)//到叶节点
   {
       if (op==0){//如果是销毁操作
       tree[root].ls=tree[root].rs=tree[root].ms=0;
       }else{
       tree[root].ls=tree[root].rs=tree[root].ms=1;//恢复
       }
       return;
   }
    int mid=MID(l,r);
    if (t<=mid){
        update(L(root),t,op);
    }else{
        update(R(root),t,op);
    }
    tree[root].ls=tree[L(root)].ls;//把左儿子节点的左连续传给父亲节点
    tree[root].rs=tree[R(root)].rs;//把右儿子节点的右连续传给父亲节点
    tree[root].ms=max(max(tree[L(root)].ms,tree[R(root)].ms),tree[L(root)].rs+tree[R(root)].ls);
    //父亲节点区间内的最大连续是由三部分构成,左右儿子的最大连续,以及左儿子的最大右连续加上右儿子的最大左连续
    if (tree[L(root)].ls == tree[L(root)].r-tree[L(root)].l+1)//如果左儿子区间满了,
        tree[root].ls+=tree[R(root)].ls;//节点的左连续应该加上右儿子的左连续
    if (tree[R(root)].rs == tree[R(root)].r-tree[R(root)].l+1)//同理右儿子节点满了
        tree[root].rs+=tree[L(root)].rs;//节点的右连续应该加上左儿子的右连续
}
int query(int root,int t)
{
    int l=tree[root].l;
    int r=tree[root].r;
    if(l==r || tree[root].ms == 0 || tree[root].ms==r-l+1){
        //到达一个叶子节点//或者里面全是呗摧毁的点//或者是区间已满的点
        return tree[root].ms;
    }
    int mid=MID(l,r);
    if (t<=mid)
    {
        if(t>=tree[L(root)].r-tree[L(root)].rs+1)//t节点在看左子树,tree[2*i].r-tree[2*i].rs+1代表左子树右边连续区间的左边界值,如果t在左子树的右区间内,则要看右子树的左区间有多长并返回
           return query(L(root),t)+query(R(root),mid+1);
     else
        query(L(root),t);//如果不在左子树的右边界内,则只需要看左子树
    }else
    {
        if (t<=tree[R(root)].l+tree[R(root)].ls-1)//看右子树的左连续的右边界,如果t在在这个范围内,需要再次访问其从左儿子右边界开始的右连续从mid开始
            return query(R(root),t)+query(L(root),mid);
        else
            return query(R(root),t);
    }
}
int main(){
  int n,m;
  while(~scanf("%d%d",&n,&m)){
      buildtree(1,1,n);
      char op;
      int tmp;
      int top;
      while(m--){
         scanf(" %c",&op);
         if (op==D){
            scanf("%d",&tmp);
           s[top++]=tmp;
           update(1,tmp,0);
         }else if(op==Q){
             scanf("%d",&tmp);
             printf("%d\n",query(1,tmp));
          }else
          {
              if(tmp>0)
              {
                  tmp=s[--top];
                  update(1,tmp,1);
              }
          }
      }
  }
  return 0;
}

HDU - 1540 线段树的合并

标签:两种   names   节点   分离   int   端点   har   algorithm   现在   

原文地址:https://www.cnblogs.com/bluefly-hrbust/p/10347261.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!