I - Tunnel Warfare HDU - 1540 (你绝对能看懂的!!!线段树)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1540

题目大意:

      有n座房子,起初都是完好的,有m次操作,可以销毁i号房屋,可以重建最后一个销毁的房屋,可以查询i号房屋和几个房屋直接或间接相连。

题目思路:

      这个明显是考察线段树的打标记,我们可以这样把问题转换一下,我们把完好的房屋记作1,把摧毁的房屋记作0,那么查询i号和几个房屋相连,就是查询i号最长连续多少个1,这样问题就简化了。

       接下来思考线段树需要什么标记,对于一次查询,我要确定和i号相连的1那么左边有右边也可能有,对于一个区间,也就是线段树上的一个节点,我只需要记录他靠近左边有多少个连续1标记为len,右边有多少个连续1标记为ren,该区间最长的连续1标记为men(max_len简称men)

        ?接下来是建树。正常建树,并且把len,ren,men都初始化为区间长度(因为还没毁)。

       ? 接下来是修改,如果是销毁,那么就把该叶子结点的len,ren,men改为0,反之改为1(此处需要一个栈记录销毁记录),肯定是需要递归到叶子结点的,然后把这个结点修改,这个好说,但是回溯的时候因为有三个标记len,ren,men。分别怎么修改呢

        1 . len :父亲结点的len等于左孩子的len,但是如果左孩子全是1,那么还要加上右孩子的len(ren同理)

        2. men:等于该节点的len;   ren   ;左儿子.ren+右儿子.len;  三者的最大值(画个图就可以看出啦)

      ?接下来是这个题的灵魂所在,查询,查询i点的时候,我要找的是他和多少个1相连,那么先思考递归终点,什么时候可以返回?

     1.叶子节点,这个不用问。

     2.整个区间都是1,因为再往下递归也是1。

     3.整个区间都是0,再往下递归也全是0。

然后如果在左儿子就要递归查询左儿子多少个连续1,但是!!!如果这个点在左儿子中到左儿子的右边界都是1,那么还要加上右儿子的len,画个图更好理解,右儿子同理。

记得每次清空栈。。。

小二,上代码 :


#include<iostream>
#include<string.h>
#include<stdio.h>
#include<stack>
using namespace std;
const int MAXN=5e4+5;
struct Point
{
    int l,r,len,ren,men;
}C[MAXN*4];
void build(int p,int l,int r)
{
    C[p].l=l;C[p].r=r;
    C[p].len=C[p].ren=C[p].men=r-l+1;
    if(l==r){return ;}
    int mid=(l+r)/2;
    build(2*p,l,mid);
    build(2*p+1,mid+1,r);
}
void change(int p,int x,int d)
{
    if(C[p].l==C[p].r){
        C[p].ren=C[p].len=C[p].men=d;
        return ;
    }
    int mid=(C[p].l+C[p].r)/2;
    if(x<=mid)change(2*p,x,d);
    else change(2*p+1,x,d);
    C[p].len=C[2*p].len;
    if(C[2*p].len==C[2*p].r-C[2*p].l+1)C[p].len+=C[2*p+1].len;
    C[p].ren=C[2*p+1].ren;
    if(C[2*p+1].ren==C[2*p+1].r-C[2*p+1].l+1)C[p].ren+=C[2*p].ren;
    C[p].men=max(C[p].len,max(C[p].ren,C[2*p].ren+C[2*p+1].len));
}
int ask(int p,int x)
{
    if(C[p].l==C[p].r||C[p].men==C[p].r-C[p].l+1||C[p].men==0){
        return C[p].men;
    }
    int mid=(C[p].l+C[p].r)/2;
    if(x<=mid)
    {
        if(C[2*p].r-x+1<=C[2*p].ren){
            return ask(2*p,x)+C[2*p+1].len;
        }
        else return ask(2*p,x);
    }
    else{
        if(x-C[2*p+1].l+1<=C[2*p+1].len){
            return ask(2*p+1,x)+C[2*p].ren;
        }
        else return ask(2*p+1,x);
    }
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
    build(1,1,n);
    stack<int>s;//
    while(s.size())s.pop();
    while(m--)
    {
        getchar();
        char c;
        scanf("%c",&c);
        if(c=='D'){
            int x;cin>>x;
            s.push(x);
            change(1,x,0);
        }
        if(c=='R'){
            int x=s.top();
            s.pop();
            change(1,x,1);
        }
        if(c=='Q'){
            int x;cin>>x;
            printf("%d\n",ask(1,x));
        }
    }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值