HDU - 1540 线段树的合并

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

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;
}

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

时间: 2024-10-12 09:46:57

HDU - 1540 线段树的合并的相关文章

HDU 4339 线段树区间合并

Query Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 2573    Accepted Submission(s): 851 Problem Description You are given two strings s1[0..l1], s2[0..l2] and Q - number of queries.Your task

hdu 3308(线段树区间合并)

LCIS Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 6069    Accepted Submission(s): 2635 Problem Description Given n integers.You have two operations:U A B: replace the Ath number by B. (index

HDU 3911 线段树区间合并

北京赛区快了,准备袭击数据结构和图论.倒计时 18天,线段树区间合并.维护一个最长连续.. 题意:给一个01串,以下有一些操作,问区间最长的连续的1的个数 思路:非常裸的线段树区间合并 #include<iostream> #include<cstdio> #include<map> #include<set> #include<cmath> #define lson id << 1 #define rson id <<

hdu 3308 线段树 区间合并+单点更新+区间查询

LCIS Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 6592    Accepted Submission(s): 2866 Problem Description Given n integers.You have two operations:U A B: replace the Ath number by B. (index

hdu 1806(线段树区间合并)

Frequent values Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1476    Accepted Submission(s): 541 Problem Description You are given a sequence of n integers a1 , a2 , ... , an in non-decreasin

I - Tunnel Warfare HDU - 1540 线段树最大连续区间

题意  :一段区间  操作1 切断点 操作2 恢复最近切断的一个点 操作3 单点查询该点所在最大连续区间 思路:  主要是push_up :  设区间x 为母区间  x<<1 ,x<<1|1分别为两个子区间 x的左端连续子段和 :当x<<1区间没有断开 也就是 x<<1 的最大连续子段ml ==tree[x<<1].r-tree[x<<1].l+1 等于区间长度时 x左端连续字段和tree[x].ll=tree[x<<1]

HDU 1540 Tunnel Warfare(区间合并)【线段树】

<题目链接> 题目大意: 题意:一个长度为n的线段,下面m个操作 D x 表示将单元x毁掉 R  表示修复最后毁坏的那个单元 Q x  询问这个单元以及它周围有多少个连续的单元,如果它本身已经被毁坏了就是0. 解题分析: 用线段树求指定点所在的最长连续区间,属于线段树区间合并类型的题,线段树的每个节点需要维护三个值,分别是对应区间的最长连续区间长度,对应区间最长连续区间前缀,对应区间最长连续后缀,然后就是在每次update之后都维护一下这三个值就行.并且注意一下query 时的操作. 1 #i

HDU 3308 LCIS (线段树区间合并)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3308 题目很好懂,就是单点更新,然后求区间的最长上升子序列. 线段树区间合并问题,注意合并的条件是a[mid + 1] > a[mid],写的细心点就好了. 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 const int MAXN = 1

HDU 5316 Magician(线段树区间合并入门)

本文纯属原创,转载请注明出处谢谢.http://blog.csdn.net/zip_fan. 题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5316 Time Limit: 18000/9000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Problem Description Fantasy magicians usually gain their ability