Tunnel Warfare HDU 1540

题目链接: Tunnel Warfare.

大致题意

有n个点排成一列,每个点与相邻的点互相连通。
现对区间【1,n】进行以下三种操作:

  1. 破坏点x的联通性
  2. 询问点x所处的最大连通区间的长度
  3. 恢复最后被破坏的点联通性

解题思路

难点

  1. 如何计算每个点的联通长度?
  2. 如何恢复最后破坏点的联通性?

解决方案

定义左(右)最大联通长度:从区间最左端(右)向右(左)前进,能联通的最大长度。

(1) 对于操作2我们可以通过维护每个子区间的最大联通左长度与最大联通右长度,再将其合并得到父亲区间的最大联通左长度与最大联通右长度。然后查询就只需通过计算两个子区间分别的左侧与右侧最大连通长度得到。

维护

inline void pushup(Tunnel &fa,Tunnel &ls,Tunnel &rs)
{
    fa.lmax = ls.lmax + (ls.lmax == ls.r-ls.l+1 ? rs.lmax : 0);
    
    fa.rmax = rs.rmax + (rs.rmax == rs.r-rs.l+1 ? ls.rmax : 0);
}
inline void pushup(int rt)
{
    pushup(a[rt],a[rt<<1],a[rt<<1|1]);
}

父亲的左最大联通长度就等于左儿子的左最大联通长度加上(),
如果左儿子的左最大联通长度等于左儿子区间长度,则()为右儿子的左最大联通长度;
否则为0,即与右儿子区间联通性断开。
同理得右。
如图:

在这里插入图片描述
在这里插入图片描述

查询

int query(int pos,int rt)
{
    if(a[rt].l==a[rt].r)
        return a[rt].lmax;
    int mid=(a[rt].l+a[rt].r)/2;
    Tunnel ls=a[rt<<1],rs=a[rt<<1|1];
    if(pos <= mid)
    {
        if((ls.r-pos)<ls.rmax)
            return ls.rmax+rs.lmax;
        else
            return query(pos,rt<<1);
    }
    else
    {
        if((pos-ls.r)<=rs.lmax)
            return rs.lmax+ls.rmax;
        else
            return query(pos,rt<<1|1);
    }
}

如果查询点落在左儿子区间中,则看它是否落在了右最长联通长度区间中。
若是,那么结果即为左儿子的右最长联通长度加上右儿子的左最长联通长度;
若不是,那么继续向下查询。
同理得右。
如图:
在这里插入图片描述
(2)对于操作3,我们可以通过栈后进先出的原理用stack解决。
(3)注意本题是多组输入

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<stack>
using namespace std;
typedef long long ll;
const int N = 50005;
struct Tunnel
{
    int l,r,lmax,rmax;
}a[N<<2];

inline void pushup(Tunnel &fa,Tunnel &ls,Tunnel &rs)
{
    fa.lmax = ls.lmax + (ls.lmax == ls.r-ls.l+1 ? rs.lmax : 0);
    fa.rmax = rs.rmax + (rs.rmax == rs.r-rs.l+1 ? ls.rmax : 0);
}

inline void pushup(int rt)
{
    pushup(a[rt],a[rt<<1],a[rt<<1|1]);
}

void build(int lef, int rig, int rt)
{
    a[rt] = {lef,rig,1,1};
    if(lef==rig)    return ;
    int mid=(lef+rig)/2;
    build(lef,mid,rt<<1);
    build(mid+1,rig,rt<<1|1);
    pushup(rt);
}

void update(int pos,int val,int rt)
{
    if(a[rt].l==a[rt].r)
    {
        a[rt].lmax=a[rt].rmax=val;
        return;
    }
    int mid=(a[rt].l+a[rt].r)/2;
    if(pos<=mid)    update(pos,val,rt<<1);
    else            update(pos,val,rt<<1|1);
    pushup(rt);
}

int query(int pos,int rt)
{
    if(a[rt].l==a[rt].r)
    {
        return a[rt].lmax;
    }
    int mid=(a[rt].l+a[rt].r)/2;
    Tunnel ls=a[rt<<1],rs=a[rt<<1|1];
    if(pos <= mid)
    {
        if((ls.r-pos)<ls.rmax)
        {
            return ls.rmax+rs.lmax;
        }
        else
            return query(pos,rt<<1);
    }
    else
    {
        if((pos-ls.r)<=rs.lmax)
        {
            return rs.lmax+ls.rmax;
        }
        else
            return query(pos,rt<<1|1);
    }
}

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        stack<int> last;
        build(1,n,1);
        while(m--)
        {
            char s[2];
            int x;
            cin>>s;
            if(s[0]=='D')
            {
                scanf("%d",&x);
                update(x,0,1);
                last.push(x);
            }
            else if(s[0]=='Q')
            {
                scanf("%d",&x);
                printf("%d\n",query(x,1));
            }
            else
            {
                update(last.top(),1,1);
                last.pop();
            }
        }
    }
    return 0;
}

总结

  • 线段树的单点修改与区间查询
  • 区间合并的维护

萌新的第一篇题解哈哈哈,如有不当之处,请多指出~

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值