HDU-1540 Tunnel Warfare (线段树+最大连续区间)

During the War of Resistance Against Japan, tunnel warfare was carried out extensively in the vast areas of north China Plain. Generally speaking, villages connected by tunnels lay in a line. Except the two at the ends, every village was directly connected with two neighboring ones.

Frequently the invaders launched attack on some of the villages and destroyed the parts of tunnels in them. The Eighth Route Army commanders requested the latest connection state of the tunnels and villages. If some villages are severely isolated, restoration of connection must be done immediately!
Input
The first line of the input contains two positive integers n and m (n, m ≤ 50,000) indicating the number of villages and events. Each of the next m lines describes an event.

There are three different events described in different format shown below:

D x: The x-th village was destroyed.

Q x: The Army commands requested the number of villages that x-th village was directly or indirectly connected with including itself.

R: The village destroyed last was rebuilt.
Output
Output the answer to each of the Army commanders’ request in order on a separate line.
Sample Input
7 9
D 3
D 6
D 5
Q 4
Q 5
R
Q 4
R
Q 4
Sample Output
1
0
2
4

kuangbin线段树专题中一道比较有意思的题目,由于我刚学线段树没多久,没有最大连续区间的概念,看了大佬的博客才知道最大连续区间是个什么东西,下面是我针对这道题的想法与思路。

题意:
有n个隧道站,m次操作,D x表示摧毁第x个隧道站,Q x 表示询问第x个隧道与其直接或间接相关的村庄数量,R表示最后毁坏的村庄被重建了。

思路:

如何求得最大连续区间

l表示该区间从左端点开始的最大连续区间
r表示该区间从右端点开始的最大连续区间,
m表示该区间的最大连续区间,(此题中,我的方法可能没有用到m)
v表示该区间全为0(-1)、全为1(1)或者0 1都有(0)(0表示该隧道摧毁,1表示该隧道仍然存在)

众所周知,该区间的状态与它的左儿子(left),右儿子(right)的状态息息相关,若我们想要知道该区间的l,r,m,v,可以从儿子的状态得到。

我们可以脑补一下,该区间的 l 与 left.l 相关。在这里,我们可以分两种情况进行讨论。
若left.v != 1(左儿子不全为1),则 l = left.l (该区间左端点最大连续为左儿子的左端点最大连续)
若left.v == 1 (左儿子全为1),则 l = left.l + right.l (因为左儿子全为1,所以左儿子与右儿子相连接,因此,该区间左端点最大连续 = 左儿子左端点的最大连续 + 右儿子左端点的最大连续)

同理,我们可以得到该区间的r值,在这里就不再重复说明

求m值时,同样只需要判断一下l,r和 left.r + right.l 三者之间的最大值即可。(要求该区间的最大连续,取 l 与 r 的最大值不必多说,当左儿子与右儿子相互连接时,此连续区间可能最大,同样要取一个最大值)

v变量的有无对代码并没有特别大的影响,主要增强代码的可读性。
当v == 1时,该区间全为1
当v == 0时,该区间既有1 也有 0
当v == -1时,该区间只有0
我们只需要判断儿子节点的v值,即可知道儿子节点当前的状态,增加代码的可读性

代码写的比较简陋,但是看懂应该还是没问题的。

void update(int rt,int L,int R,int x,int val)
{
    if(L == R){
        if(val == 1){
            tree[rt].l = tree[rt].r = tree[rt].m = tree[rt].v = 1;
        }
        else{
            tree[rt].l = tree[rt].r = tree[rt].m = 0;
            tree[rt].v = -1;
        }
        return ;
    }

    int Mid = (L+R)>>1;
    if(x <= Mid)   update(rt<<1,L,Mid,x,val);
    else update(rt<<1|1,Mid+1,R,x,val);

    if(tree[rt<<1].v == 1)
        tree[rt].l = tree[rt<<1].l+tree[rt<<1|1].l;
    else tree[rt].l = tree[rt<<1].l;                //该区间左端最长连续

    if(tree[rt<<1|1].v == 1)
        tree[rt].r = tree[rt<<1|1].r + tree[rt<<1].r;
    else tree[rt].r = tree[rt<<1|1].r;              //该区间右端最长连续

    tree[rt].m = max(max(tree[rt].l,tree[rt].r),tree[rt<<1].r+tree[rt<<1|1].l);     //该区间最长连续

    if(tree[rt<<1].v==1 && tree[rt<<1|1].v==1)  tree[rt].v = 1;     //判断该区间全为0 或全为1 或0 1 都有
    else if(tree[rt<<1].v==-1 && tree[rt<<1|1].v==-1)   tree[rt].v = -1;
    else tree[rt].v = 0;
}

查询与点x相连的个数

刚刚开始写查询的时候,经常有一些特殊点不能处理好,例如:我最开始用的方法是 求以x为左端点的最大连续,以x为右端点的最大连续,但是就是有一些特殊点处理不好,所以就换了一种方法。

我们从如何求最大连续区间中可以得到两个已知最大连续 l r ,那么我们只要确定x处于某个区间中左端最大连续或者右端最大连续即可,那么我们就可以确定与x想连的点的个数。

从上述中,我们已经确定了大概的方法,我们只需要知道x位于某个区间中的左端最大连续或者右端最大连续即可。但是每次都要判断该区间的左端与右端,存在重复判断或者不需要判断的情况,我们可以给他进行一个简单的优化。

假设,我们有下表中的一个区间(标记的为左儿子,未标记的是右儿子),需要查询点5.

此时,我们需要确定x == 5的位置,显而易见,x处于左儿子中。如果说此时我们判断它是在左儿子的 l 或者 r ,那么,从图中我们可以很显然的看到,由于 x==5 不在 l 与 r 中,因此我们需要再查询下一个区间。

123456789101112
110110110101

在当前区间中,我们可以看到,x == 5处于右儿子中,那么上一个区间的 l 是否就可以不用再查询了呢?即在区间[1,12]的左儿子中,我们就不用在查询 l 。

123456
110110

因此,我们可以得到以下的结论。

在查询x位于该区间的左儿子或者右儿子时
当x在左儿子中,我们只需要查询x是否位于left.r即可,若存在,ans = left.r + right.l(不要忘了left.r是与right.l相连的),若不存在,继续向下查询。
当x在右儿子中,同理可得!

void query(int rt,int L,int R,int x)    //查询与该点相连的点的个数
{
    if(L ==R){
        if(tree[rt].m == 1) ans = 1;
        else ans = 0;
        return ;
    }
    int Mid = (L+R)>>1;
    if(x <= Mid){               //判断该点是否在左儿子上
        if(x >= Mid-tree[rt<<1].r+1){   //如果该点在右端点的最大连续区间中
            ans = tree[rt<<1].r + tree[rt<<1|1].l;
            return ;
        }
        query(rt<<1,L,Mid,x);
    }
    if(Mid < x){                //判断该点是否在右儿子上
        if(x <= Mid + tree[rt<<1|1].l){ //如果该点在左端点的最大连续区间中
            ans = tree[rt<<1|1].l + tree[rt<<1].r;
            return ;
        }
        query(rt<<1|1,Mid+1,R,x);
    }
}

下面是完整的代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const int MAXN = 51000;
struct node
{
    int l,r,m,v;
}tree[MAXN*4];
int ans;

void build(int rt,int L,int R)
{
    tree[rt].l = tree[rt].r = tree[rt].m = R-L+1;
    tree[rt].v = 1;
    if(L == R)  return ;
    int Mid = (L+R)>>1;
    build(rt<<1,L,Mid);
    build(rt<<1|1,Mid+1,R);
}

void update(int rt,int L,int R,int x,int val)
{
    if(L == R){
        if(val == 1){
            tree[rt].l = tree[rt].r = tree[rt].m = tree[rt].v = 1;
        }
        else{
            tree[rt].l = tree[rt].r = tree[rt].m = 0;
            tree[rt].v = -1;
        }
        return ;
    }

    int Mid = (L+R)>>1;
    if(x <= Mid)   update(rt<<1,L,Mid,x,val);
    else update(rt<<1|1,Mid+1,R,x,val);

    if(tree[rt<<1].v == 1)
        tree[rt].l = tree[rt<<1].l+tree[rt<<1|1].l;
    else tree[rt].l = tree[rt<<1].l;                //该区间左端最长连续

    if(tree[rt<<1|1].v == 1)
        tree[rt].r = tree[rt<<1|1].r + tree[rt<<1].r;
    else tree[rt].r = tree[rt<<1|1].r;              //该区间右端最长连续

    tree[rt].m = max(max(tree[rt].l,tree[rt].r),tree[rt<<1].r+tree[rt<<1|1].l);     //该区间最长连续

    if(tree[rt<<1].v==1 && tree[rt<<1|1].v==1)  tree[rt].v = 1;     //判断该区间全为0 或全为1 或0 1 都有
    else if(tree[rt<<1].v==-1 && tree[rt<<1|1].v==-1)   tree[rt].v = -1;
    else tree[rt].v = 0;
}

void query(int rt,int L,int R,int x)    //查询与该点相连的点的个数
{
    if(L ==R){
        if(tree[rt].m == 1) ans = 1;
        else ans = 0;
        return ;
    }
    int Mid = (L+R)>>1;
    if(x <= Mid){               //判断该点是否在左儿子上
        if(x >= Mid-tree[rt<<1].r+1){   //如果该点在右端点的最大连续区间中
            ans = tree[rt<<1].r + tree[rt<<1|1].l;
            return ;
        }
        query(rt<<1,L,Mid,x);
    }
    if(Mid < x){                //判断该点是否在右儿子上
        if(x <= Mid + tree[rt<<1|1].l){ //如果该点在左端点的最大连续区间中
            ans = tree[rt<<1|1].l + tree[rt<<1].r;
            return ;
        }
        query(rt<<1|1,Mid+1,R,x);
    }
}

int main()
{
    int n,m;
    int p[510000],top=0;
    while(~scanf("%d%d",&n,&m)){
        build(1,1,n);
        while(m--){
            char ch;
            int x;
            cin>>ch;
            if(ch=='D'){
                scanf("%d",&x);
                p[top++]=x;
                update(1,1,n,x,0);
            }
            else if(ch == 'R'){
                update(1,1,n,p[--top],1);
            }
            else{
                scanf("%d",&x);
                query(1,1,n,x);
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}

如果大家发现有问题的话,还请大家尽情指出~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值