HDU1540 Tunnel Warfare

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=1540

题目

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 

Q 4 

Q 4

Sample Output 



4

题意

给定一个n个完好的村庄。编号1~n。对这些村庄进行如下操作: 
D x:破坏第x个村庄 
Q x:查询包含第x个村庄的连续完好村庄的最大值 
R:重建最后一个被破坏的村庄

分析

和“求连续k个空位”POJ3667 Hotel这题很像。 
采用维护结点信息:lsun,rsum,sum来解决。 
lsum表示从区间最左侧往右的最大连续区间. 
rsum表示从区间最右侧往左的最大连续区间. 
sum表示该区间的最大连续区间.

确定结点信息后,进行单点修改很好实现。D x将第x个的lsum,rsum,sum修改为1,R将栈顶端的元素的信息修改为0。 
关键是查询,怎么根据所维护的结点信息lsum,rsum,sum来查询含第k位的最大连续区间。 
不像常规的区间查询,将该区间分成若干个线段树上的子区间,分别获取信息并统计即可。 
也不像常规的单点查询,将该点的信息获取即可。 
用query(p,k)去获取线段树的p结点上包含第k位村庄的最大连续区间。但是结点信息没有说包含第k位,只是该区间上的最大连续区间,不能直接用该结点的sum、lsum、rsum作为答案。需要分类讨论k的位置情况通过手动合并区间信息来确定答案: 
(1)k在p的左子树上 
(1.1)k在p的左子树的最右区间上: 
那么左子树的最右区间T[lson].rsum+右子树的最左区间T[rson].lsum即为所求. 
(1.2)k在p的左子树的非最右区间上: 
递归进左子树查询. 
(2)k在p的右子树上 
同理。

#include <cstdio>
#include <cstring>
#include <stack>
#define inf 0x3f3f3f3f
#define lson p<<1
#define rson p<<1|1
using namespace std;
const  int maxn=5e4+100;
struct node
{
    int l,r;//结点所维护的区间
    int lsum,rsum,sum;
}T[maxn<<2];//线段树要开四倍空间

void up(int p)
{
    //区间信息的维护,将左子树所表示的区间与右子树所表示的区间合并
    T[p].lsum=T[lson].lsum;
    T[p].rsum=T[rson].rsum;
    if(T[p].lsum==T[lson].r-T[lson].l+1) T[p].lsum+=T[rson].lsum;
    if(T[p].rsum==T[rson].r-T[rson].l+1) T[p].rsum+=T[lson].rsum;
    T[p].sum=max(T[lson].sum,max(T[rson].sum,T[lson].rsum+T[rson].lsum));
}

void build(int p,int l,int r)//p表示结点编号,l、r表示结点p所表示的区间
{
    T[p].l=l,T[p].r=r;//确定结点p所表示的区间[l,r]
    if(l==r) //确定叶子结点所表示的信息
    {
        T[p].lsum=T[p].rsum=T[p].sum=1;
        return ;
    }
    int mid=(l+r)>>1;
    build(lson,l,mid); //递归创建左子树
    build(rson,mid+1,r); //递归创建右子树
    up(p); //由下向上传递信息,即由两个小区间合并成一个大区间

}
void update(int p,int k,int v)//单点修改
{
    if(T[p].l==T[p].r) //找到叶子结点
    {
        T[p].lsum=T[p].rsum=T[p].sum=v; //修改叶子结点信息
        return ;
    }
    int mid=(T[p].l+T[p].r)>>1;
    if(k<=mid) update(lson,k,v); //k叶子结点在左子树上
    else update(rson,k,v); //k叶子结点在右子树上
    up(p); //修改包含结点的区间
}
int query(int p,int k) //单点查询及优化:查询到连续区间就可终止
{
    if(T[p].l==T[p].r) return T[p].sum;//查到叶子结点
    //if(T[p].sum==T[p].r-T[p].l+1) return T[p].sum;//优化:查到连续区间
    //下面是关键
    //在查询的某些过程中进行区间合并查询
    int mid=(T[p].l+T[p].r)>>1;
    if(k<=mid) //在左子树上,但也可能与右子树的信息有关
    {
        //需要借助右子树的信息
        if(k>=T[lson].r-T[lson].rsum+1)//在靠着右子树的连续区间上
        {
            int ans=0;
            ans+=T[lson].rsum;//包含左子树的最右连续区间
            ans+=T[rson].lsum;//包含右子树的最左连续区间
            return ans;
        }
        else return query(lson,k);
    }
    else
    {
        if(k<=T[rson].l+T[rson].lsum-1)
        {
            int ans=0;
            ans+=T[rson].lsum;
            ans+=T[lson].rsum;
            return ans;
        }
        else return query(rson,k);
    }
}

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值