poj(2892)——Tunnel Warfare(成段更新,寻找空间)

一道ACM竞赛题目,涉及到Tunnel Warfare的解决方案,使用线段树进行求解。题目要求处理房屋摧毁与修复,实现查询和更新操作。线段树在此问题中用于高效查询和更新,结合lazy思想处理区间合并。解答过程中探讨了线段树在满、空、子节点情况下的处理方式,并详细解释了如何判断不同情况下区间是否连续。

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

题目大意是:

给你n个房子,然后有m个操作:

(1):D x:代表摧毁第x个村庄

(2):Q x:查询与第x个村庄直接或间接(即为通过其他没有被炸毁的村庄相连接的)的村子的个数

(3):R :代表的是最后被摧毁的那个村庄被修复(这里就要用到栈了,哈哈)

唔。。。这道线段树的题目想了很久也没啥思路,网上说和poj hotel的那题很像,但是一直联系不起来,但是想通了也就这样吧。

其实线段树是一个很灵活的东西,它是辅助功能,这里它的作用是为了query(查询操作)与update(更新操作)。

当然其中还有一个lazy-tag的思想,那就是向上更新的时候,这个是经典的区间合并的操作,(其实我觉得叫这名字有点怪怪的感觉。。。

void pushup(int v){
	int temp=v<<1;
	tree[v].ls=tree[temp].ls;
	tree[v].rs=tree[temp+1].rs;
	if(tree[v].ls==tree[temp].r-tree[temp].l+1) tree[v].ls+=tree[temp+1].ls;
	if(tree[v].rs==tree[temp+1].r-tree[temp+1].l+1) tree[v].rs+=tree[temp].rs;
	tree[v].ms=max(tree[temp].rs+tree[temp+1].ls,max(tree[temp].ms,tree[temp+1].ms));
}

首先讲讲更新操作,这里我们是把它更新到子节点,然后利用lazy思想在往上更新。

void update(int pos,int v,int cnt){
	if(tree[v].l==tree[v].r&&tree[v].l==pos){
		if(cnt==0) tree[v].ls=tree[v].rs=tree[v].ms=0;
		else tree[v].ls=tree[v].rs=tree[v].ms=1;
		return;
	}
	int mid=(tree[v].l+tree[v].r)>>1;
	int temp=v<<1;
	if(pos<=mid) update(pos,temp,cnt);
	else update(pos,temp+1,cnt);
	pushup(v);
}

最重要的要数查找了吧(query)。

首先我们知道如果一个节点如果它满足当前节点是满的或是它是全空的,或是它是子节点,那么我们就返回当前节点的ms值(代表的是这个区间中的最大连续区间个数),如果不是,我们要分两种情况:

(1)首先当前节点是在左儿子的地方,如果pos这个我们要求的点是在左儿子的右区间内的话,那么我们还要再去判断一下是不是在右儿子上也有一段区间与左儿子相连,这也就是为什么要query(pos,temp)+query(pos,temp+1),后面的那个的目的就是为了查询判断一下在右儿子中是否还有一段连续的区间使得也刚好与左儿子相连 。

(2)讨论当前节点是在右儿子的地方,思路也是同样的。

如图中画黄线的所示:如果我们要查询2,那么我们还要判断右儿子上还有没有连起来的区间。


int query(int pos,int v){
	if(tree[v].ms==0||tree[v].ms==tree[v].r-tree[v].l+1||tree[v].l==tree[v].r){
		return tree[v].ms;
	}
	int temp=v<<1;
	int mid=(tree[v].l+tree[v].r)>>1;
	if(pos<=mid){
		if(pos>=tree[temp].r-tree[temp].rs+1) return query(pos,temp)+query(mid+1,temp+1);
		else return query(pos,temp);
	}
	else{
		if(pos<=tree[temp+1].l+tree[temp+1].ls-1) return query(mid,temp)+query(pos,temp+1);
		else return query(pos,temp+1);
	}
}

总之挺不错的一道线段树的题目,一开始我一直不知道要怎么把线段树给用上去。。。

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
stack<int> sk;
#define maxn 55555
struct node{
	int l,r;
	int ls,rs,ms;
}tree[maxn*4];
void pushup(int v){
	int temp=v<<1;
	tree[v].ls=tree[temp].ls;
	tree[v].rs=tree[temp+1].rs;
	if(tree[v].ls==tree[temp].r-tree[temp].l+1) tree[v].ls+=tree[temp+1].ls;
	if(tree[v].rs==tree[temp+1].r-tree[temp+1].l+1) tree[v].rs+=tree[temp].rs;
	tree[v].ms=max(tree[temp].rs+tree[temp+1].ls,max(tree[temp].ms,tree[temp+1].ms));
}
void build(int l,int r,int v){
	tree[v].l=l;
	tree[v].r=r;
	tree[v].ls=tree[v].rs=tree[v].ms=r-l+1;
	if(l==r) return;
	int mid=(l+r)>>1;
	int temp=v<<1;
	build(l,mid,temp);
	build(mid+1,r,temp+1);
}
int query(int pos,int v){
	if(tree[v].ms==0||tree[v].ms==tree[v].r-tree[v].l+1||tree[v].l==tree[v].r){
		return tree[v].ms;
	}
	int temp=v<<1;
	int mid=(tree[v].l+tree[v].r)>>1;
	if(pos<=mid){
		if(pos>=tree[temp].r-tree[temp].rs+1) return query(pos,temp)+query(mid+1,temp+1);
		else return query(pos,temp);
	}
	else{
		if(pos<=tree[temp+1].l+tree[temp+1].ls-1) return query(mid,temp)+query(pos,temp+1);
		else return query(pos,temp+1);
	}
}
void update(int pos,int v,int cnt){
	if(tree[v].l==tree[v].r&&tree[v].l==pos){
		if(cnt==0) tree[v].ls=tree[v].rs=tree[v].ms=0;
		else tree[v].ls=tree[v].rs=tree[v].ms=1;
		return;
	}
	int mid=(tree[v].l+tree[v].r)>>1;
	int temp=v<<1;
	if(pos<=mid) update(pos,temp,cnt);
	else update(pos,temp+1,cnt);
	pushup(v);
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	build(1,n,1);
	while(!sk.empty()){
		sk.pop();
	}
	char a[5];
	int x;
	while(m--){
		scanf("%s",a);
		if(a[0]=='D'){
			scanf("%d",&x);
			sk.push(x);
			update(x,1,0);
		}
		else if(a[0]=='Q'){
			scanf("%d",&x);
			printf("%d\n",query(x,1));
		}
		else{
			x=sk.top();
			sk.pop();
			update(x,1,1);
		}
	}
}

加油,wish everyone can achieve their dream!【Acmer ,fighting~ ∶)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值