hdu1540-Tunnel Warfare 线段树区间合并

本文介绍了一个模拟抗日战争期间地道战的算法问题,通过线段树数据结构实现对村庄连续状态的查询与更新,解决了村庄被破坏后的连接状态问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Tunnel Warfare

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 9013    Accepted Submission(s): 3479


Problem Description
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的村庄是相互连接的,然后给出一系列操作,D表示让村庄消失,Q表示询问当前状态下最长的连接的村庄的数量,R表示恢复最后消失的村庄。

解题思路: 这道题是求最长连续区间,可以使用线段树来解决,存在的村庄置1,消失的置0,然后我们需要使用一个栈来存贮消失的村庄,用于村庄恢复,在这里我们需要使用到的是区间合并知识,所谓区间合并就是当一个区间的左区间是满的时候则这个区间的左区间长度为左区间长度加上右区间的左区间长度,当一个区间的右区间是满的时候,这个区间的右区间长度为右区间长度加上左区间的右区间长度,具体可以参看代码描述,这里都是使用递归的思想来解决问题。

ac代码:
#include <cstdio>
#include <iostream>
using namespace std;
#define Max 50005
int s[Max], top;//想当于栈的作用 
struct node{
	int left, right, maxn;//记录连续左区间长度、连续右区间长度、连续的最大长度 
}sum[Max<<2];
void Build(int l, int r, int rt){
	sum[rt].left = sum[rt].right = sum[rt].maxn = r - l +1; //初始都是1所以赋值为区间长度 
	if(l == r)	return ;
	int m = (l + r) >> 1;//递归建树 
	Build(l, m, rt<<1);
	Build(m+1, r, rt<<1|1);
}
void update(int l, int r, int t, int x, int rt)//更新节点 
{
	if(l == r){//搜索到叶子节点 位置 
		if(x == 1) sum[rt].left = sum[rt].right = sum[rt].maxn = 1;//恢复村庄 
		else sum[rt].left = sum[rt].right = sum[rt].maxn = 0;//让村庄消失 
		return ;
	}
	int m = (l + r) >> 1;
	if(t <= m) update(l, m, t, x, rt<<1);//回溯向上更新 
	else update(m+1, r, t, x, rt<<1|1);
	sum[rt].left = sum[rt<<1].left;
	sum[rt].right = sum[rt<<1|1].right;
	sum[rt].maxn = max(max(sum[rt<<1].maxn, sum[rt<<1|1].maxn), sum[rt<<1].right + sum[rt<<1|1].left);//很重要 
	if(sum[rt<<1].left == m - l + 1) sum[rt].left += sum[rt<<1|1].left;//左区间满,合并 
	if(sum[rt<<1|1].right == r - m ) sum[rt].right += sum[rt<<1].right;//右区间满,合并 
}
int query(int l, int r, int t, int rt)//查询最大连续区间长度 
{
	if(l == r || sum[rt].maxn == 0 || sum[rt].maxn == r - l + 1){//当搜索到叶子节点或者空节点或者节点已满 
		return sum[rt].maxn;
	}
	int m = (l + r) >> 1;
	if(t <= m){
		if(t >= m - sum[rt<<1].right +1)//因为t<=mid,看左子树,a[2*i].r-a[2*i].rs+1代表左子树右边连续区间的左边界值,如果t在左子树的右区间内,则要看右子树的左区间有多长并返回  
		  return query(m+1, r, m+1, rt<<1|1) + query(l, m, t, rt<<1);
		else return query(l, m, t, rt<<1);//如果不在左子树的右边界区间内,则只需要看左子树  
	}
	else{
		if(t <= m+1 + sum[rt<<1|1].left -1)//同理 
		   return query(m+1, r, t, rt<<1|1) + query(l, m, m, rt<<1);
		else return query(m+1, r, t, rt<<1|1);
	}
}
int main()
{
	int n, m, x;
	char str[3];
	while(~scanf("%d%d", &n, &m)){
		top = 0;
	Build(1, n, 1);
	while(m--)
	{
		scanf("%s", str);
		if(str[0] == 'D'){
			scanf("%d", &x);
			s[top++] = x;//入栈 
			update(1, n, x, 0, 1);
		}
		else if(str[0] == 'Q'){
			scanf("%d", &x);
			printf("%d\n", query(1, n, x, 1));
		}
		else if(str[0] == 'R'){
			if(x > 0)
			{
				 x = s[--top];//出栈 
			update(1, n, x, 1, 1);
			}
		}
	}
	}
	return 0;
}

题目链接地址: 点击打开链接http://acm.hdu.edu.cn/showproblem.php?pid=1540
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值