HDU - 1540 Tunnel Warfare(线段树)

线段树真的是一个很强大的数据结构,对于很多区间动态问题都可以解答。奈何我太菜,因为这种题往往需要很多的语言描述才能把码说明白,写博客写的很煎熬,归根到底还是我太菜。这道题很有营养,我来记一下。

题目大意:输入n,m:n表示有n个城市相连,m表示下面有m行指令。D X表示破坏X这个城市,R表示修复最近一次破坏的城市,Q X表示查询以X点为中心的整体连续中最大的连续个数并输出。

线段树怎么解呢?首先确定好线段树之后,我们要确定线段树要存储区间的什么内容。对于简单的rmq要存储最大最小值,对于区间求和要存区间的和,对于区间染色问题我们要记录这个区间染色了没有,被多少个染色。对于本题而言,我们要求连续的最大区间,所以我们应该存储区间的值,但是一个线段有多个区间,我们要想,一个区间包含两个小区间,那怎么通过两个小区间来更新大区间呢?答案是我们可以存储三个值,第一个值表示从左开始最大的连续区间,第二个值表示从右开始的最大区间,第三个值用来表示这个区间内最大的连续区间。大区间可以是两个小区间内最大的那个区间,也可能是左小区间的最大连续右区间和右小区间的最大连续左区间之和。这样的话,这道题变得简单了很多。代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
 
using namespace std;
 
struct node
{
    int l,r;
    int ls,rs,ms;//分别表示左边最大连续,右边最大连续,以及整个区间内的最大连续长度
} s[50050*3];
 
int n,m;
int op[50010];
 
void InitTree(int l,int r,int k)
{
    s[k].l=l;
    s[k].r=r;
    s[k].ls=s[k].rs=s[k].ms=r-l+1;//最开始的时候全都是连着的。所以长度为r-l+1
    if (l==r)
        return ;
    int mid=(l+r)/2;
    InitTree(l,mid,k*2);
    InitTree(mid+1,r,k*2+1);
}
 
void UpdataTree(int x,int flag,int k)//x表示修复或者破坏的位置,flag用来标记是破坏还是修复
{
    if (s[k].l==s[k].r)
    {
        if (flag==1)
            s[k].ls=s[k].rs=s[k].ms=1;//修复
        else
            s[k].ls=s[k].rs=s[k].ms=0;//破坏
        return ;
    }
    int mid=(s[k].l+s[k].r)/2;
    if (x<=mid)
        UpdataTree(x,flag,2*k);
    else
        UpdataTree(x,flag,2*k+1);
    if(s[2*k].ls == s[2*k].r-s[2*k].l+1)//左区间的左连续=左子树的长度,就说名左区间的数全部连续,(左子树区间满了),整个区间的左区间就应该加上有区间的左部分。
        s[k].ls =s[2*k].ls+s[2*k+1].ls;
    else
        s[k].ls=s[2*k].ls;
    if(s[2*k+1].rs==s[2*k+1].r-s[2*k+1].l+1)//同理
        s[k].rs=s[2*k+1].rs+s[2*k].rs;
    else
        s[k].rs=s[2*k+1].rs;
    s[k].ms=max(max(s[2*k].ms,s[2*k+1].ms),s[2*k].rs+s[2*k+1].ls);//整个区间内的最大连续应为:左子树最大区间,右子树最大区间,左右子树合并的中间区间,三者中取最大
}
 
int SearchTree(int x,int k)
{
    if(s[k].l==s[k].r||s[k].ms==0||s[k].ms==s[k].r-s[k].l+1)//到了叶子节点或者该访问区间为空或者已满都不必要往下走了
        return s[k].ms;
    int mid=(s[k].l+s[k].r)/2;
    if (x<=mid)
    {
        if (x>=s[2*k].r-s[2*k].rs+1)//判断当前这个数是否在左区间的右连续中,其中s[2*k].r-s[2*k].rs+1代表左子树右边连续区间的左边界值,即有连续区间的起点
            return s[2*k].rs+s[2*k+1].ls;//也可以SearchTree(x,2*k)+SearchTree(mid+1,2*k+1);
        else
            return SearchTree(x,2*k);
    }
    else
    {
        if (x<=s[2*k+1].l+s[2*k+1].ls-1)//判断当前这个数是否在左区间的右连续中,其中s[2*k].r-s[2*k].rs+1代表左子树右边连续区间的左边界值,即有连续区间的起点
            return s[2*k].rs+s[2*k+1].ls;//这种方法SearchTree(x,2*k+1)+SearchTree(mid,2*k);也是可以的,但是比较浪费时间
        else
            return SearchTree(x,2*k+1);
    }
}
 
int main()
{
    int x;
    char ch[2];
    while (~scanf ("%d%d",&n,&m))
    {
        int top=0;
        InitTree(1,n,1);
        while (m--)
        {
            scanf("%s",ch);
            if (ch[0]=='D')
            {
                scanf("%d",&x);
                op[top++]=x;
                UpdataTree(x,0,1);
            }
            else if (ch[0]=='Q')
            {
                scanf("%d",&x);
                printf ("%d\n",SearchTree(x,1));
            }
            else
            {
                if (x>0)
                {
                    x=op[--top];
                    UpdataTree(x,1,1);
                }
            }
        }
    }
    return 0;
}

代码借鉴于https://blog.csdn.net/qiqi_skystar/article/details/50338079(PS:我不仅菜,还懒,谢谢大佬的代码,受益匪浅)

猜你喜欢

转载自blog.csdn.net/qq_38367681/article/details/81273982