当前位置: 代码迷 >> 综合 >> HDU1540 Tunnel Warfare (求包含第k位的最大连续区间)
  详细解决方案

HDU1540 Tunnel Warfare (求包含第k位的最大连续区间)

热度:95   发布时间:2023-11-22 00:43:58.0

题目链接

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
R
Q 4
R
Q 4

Sample Output
1
0
2
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的右子树上
同理。

AC代码

//561ms 4.2MB
#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;
}