HDU-1540 Tunnel Warfare 地道战 (线段树求相连区间长度)

本文介绍了一种使用线段树数据结构解决战争中隧道连接状态查询的问题,通过单点修改和区间查询,实现对村庄连接状态的快速更新与查询,特别适用于村庄遭受攻击或修复后的最新连接状态获取。

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

这里是一条通往HDU的隧道        

Tunnel Warfare

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

 

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

地道战时期,每个村庄都是相连的,但是鬼子会来摧毁村庄,而八路则会去修复村庄,修复的是上一个鬼子破坏的村庄,给你指定一个村庄,问你和这个村庄连接的村庄有多少个?

学姐挂题的时候告诉我们说这道题可以暴力求解,嘻嘻,刚看到这个题的时候我想的也是暴力,暴力出奇迹嘛。结果我成功的暴毙~_~,后来才知道学姐说的是用二分暴力,不过我没写,还是练练我垃圾的线段树吧

其实这道题就是一个单点修改和区间查询的过程,一开始所有的村庄都是连接的,我们标记每个村庄为1,被摧毁之后我们就把它标记为0,求得连续为1的区间长度就行了呗

因为这是第一道线段树进阶的题目,所以说板子啥的都不知道,就有点费劲,其实有点底子和区间合并的意识都可以看得懂

先来说一下这种题的第一个板子吧:up向上更新函数

void up(ll k){
	pp[k].lmx=pp[k<<1].lmx;
	if(pp[k].lmx==pp[k<<1].r-pp[k<<1].l+1)//左边的全部连接 可以考虑一下右边 
		pp[k].lmx+=pp[k<<1|1].lmx;
	//同理 
	pp[k].rmx=pp[k<<1|1].rmx;
	if(pp[k].rmx==pp[k<<1|1].r-pp[k<<1|1].l+1)
		pp[k].rmx+=pp[k<<1].rmx;
		
	pp[k].mx=max(max(pp[k<<1].mx,pp[k<<1|1].mx),pp[k<<1].rmx+pp[k<<1|1].rmx);
}

摧毁和修复村庄都比较简单,单点修改即可,最难的就是区间查询部分

ll query(ll k,ll x){//查询与x点相邻的最长段 
	//点,区间长度为0,区间全部连接 
	if(pp[k].l==pp[k].r||pp[k].mx==0||pp[k].mx==pp[k].r-pp[k].l+1)	
		return pp[k].mx;
	ll mid=(pp[k].l+pp[k].r)>>1;
	
	if(x<=mid){//在左边的话,要注意最靠左或者中间 
		if(x>=mid-pp[k<<1].rmx+1)//靠近中间位置 ,且到中间都连续 
			return query(k<<1,x)+query(k<<1|1,mid+1);//向右延伸一下 
		else return query(k<<1,x);
	}
	else {//同理 
		if(x<=mid+pp[k<<1|1].lmx)
			return query(k<<1|1,x)+query(k<<1,mid);
		else return query(k<<1|1,x);
	}
}

需要根据指定点的位置,来判断他能不能向两边延伸,在边上直接求解即可,中间部分一定要想着向对称轴子树的边界延伸

还有就是return边界的判断,鄙人不会,还是跟青大第二线段树大哥学的,可能过段时间就变成第一了吧,反正膜拜大佬就对了

Orz 

还有就是看到这道题想到的第一个知识点,用来保存被摧毁的村庄,嘻嘻嘻

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<stack>
#define ll long long			
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const ll inf=0x3f3f3f3f;
const ll mm=5e4+10;

stack<int>s;
struct node{
	ll l,r,lmx,rmx,mx;
}pp[mm<<2];

void up(ll k){
	pp[k].lmx=pp[k<<1].lmx;
	if(pp[k].lmx==pp[k<<1].r-pp[k<<1].l+1)//左边的全部连接 可以考虑一下右边 
		pp[k].lmx+=pp[k<<1|1].lmx;
	//同理 
	pp[k].rmx=pp[k<<1|1].rmx;
	if(pp[k].rmx==pp[k<<1|1].r-pp[k<<1|1].l+1)
		pp[k].rmx+=pp[k<<1].rmx;
		
	pp[k].mx=max(max(pp[k<<1].mx,pp[k<<1|1].mx),pp[k<<1].rmx+pp[k<<1|1].rmx);
}

void build(ll k,ll l,ll r){
	pp[k].l=l;
	pp[k].r=r;
	pp[k].lmx=pp[k].rmx=pp[k].mx=r-l+1;//
	if(l==r)return ;
	
	ll mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	up(k);	
}

void change(ll k,ll x,ll y){//将x位置的数改成y 
	if(pp[k].l==pp[k].r){
		pp[k].lmx=pp[k].rmx=pp[k].mx=y;
		return ;
	} 
	ll mid=(pp[k].l+pp[k].r)>>1;
	if(x<=mid)
		change(k<<1,x,y);
	else change(k<<1|1,x,y);
	up(k);
}
//
ll query(ll k,ll x){//查询与x点相邻的最长段 
	//点,区间长度为0,区间全部连接 
	if(pp[k].l==pp[k].r||pp[k].mx==0||pp[k].mx==pp[k].r-pp[k].l+1)	
		return pp[k].mx;
	ll mid=(pp[k].l+pp[k].r)>>1;
	
	if(x<=mid){//在左边的话,要注意最靠左或者中间 
		if(x>=mid-pp[k<<1].rmx+1)//靠近中间位置 ,且到中间都连续 
			return query(k<<1,x)+query(k<<1|1,mid+1);//向右延伸一下 
		else return query(k<<1,x);
	}
	else {//同理 
		if(x<=mid+pp[k<<1|1].lmx)
			return query(k<<1|1,x)+query(k<<1,mid);
		else return query(k<<1|1,x);
	}
}

int main()
{	
	ll n,m;
	while(cin>>n>>m){
		build(1,1,n);
		char op[2];
		ll x;
		while(m--){
			cin>>op;
			if(op[0]=='D'){
				cin>>x;
				s.push(x);	
				change(1,x,0);
			}
			else if(op[0]=='R'){
				ll last=s.top();
				s.pop();
				change(1,last,1);
			}
			else {
				cin>>x;
				cout<<query(1,x)<<endl;
			}	
		}
	} 
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值