HDU - 1540(Tunnel Warfare)二分+线段树

题目链接:Tunnel Warfare - HDU 1540 - Virtual Judge (ppsucxtt.cn)

摧毁村庄和重建村庄都比较容易,就是一个线段树的单点修改操作,关键是如何查询与一个村庄直接或间接相关的村庄数量。两个相邻村庄相关需要满足什么条件呢?条件就是他们都没有被摧毁,那么我们就可以类似得到 l,l+1,……,r这些村庄相关的条件就是他们的区间和等于r-l+1,于是我们就知道了如何判断一个区间的村庄是否相关,因此我们就可以二分判断一个村庄左边和右边与它相关的村庄数目,然后加起来减一即可(因为这个村庄被计算了两次)。

注意这道题目有个坑点,就是一个村庄可能被重复摧毁,举个例子,假如我们摧毁村庄的顺序是

1 2 2,那么我们修复的第一个村庄是2,第二个村庄还是2,最后一个修复的村庄才是1,这就是本题的坑点所在,其他就没什么了,下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e6+10;
int l[N],r[N],sum[N],st[N];
bool vis[N];
void pushup(int id)
{
	sum[id]=sum[id<<1]+sum[id<<1|1];
}
void build(int id,int L,int R)
{
	sum[id]=0;l[id]=L;r[id]=R;
	if(L==R)
	{
		sum[id]=1;
		return ;
	}
	int mid=L+R>>1;
	build(id<<1,L,mid);
	build(id<<1|1,mid+1,R);
	pushup(id);
	return ;
}
int query_interval(int id,int L,int R)
{
	if(l[id]>=L&&r[id]<=R) return sum[id];
	int mid=l[id]+r[id]>>1;
	int ans=0;
	if(mid>=L) ans+=query_interval(id<<1,L,R);
	if(mid+1<=R) ans+=query_interval(id<<1|1,L,R);
	return ans;
}
void update_point(int id,int x,int val)
{
	if(l[id]==r[id])
	{
		sum[id]=val;
		return ;
	}
	int mid=l[id]+r[id]>>1;
	if(x<=mid) update_point(id<<1,x,val);
	else update_point(id<<1|1,x,val);
	pushup(id); 
	return ;
}
int main()
{
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		build(1,1,n);
		for(int i=1;i<=n;i++)
			vis[i]=false;
		int x;
		char op[5];
		int tt=0;
		while(m--)
		{
			scanf("%s",op);
			if(op[0]=='D')
			{
				scanf("%d",&x);
				if(!vis[x]) update_point(1,x,0);
				vis[x]=true;//标记为被摧毁(可能会被重复加入)
				st[++tt]=x;//记录最后毁坏的村庄 
			} 
			else if(op[0]=='Q')
			{
				scanf("%d",&x);
				int ans=0;
				if(vis[x])//如果村庄已经被摧毁,则直接输出0 
				{
					printf("0\n");
					continue;
				}
				int ll=1,rr=x;
				//二分找左边与当前村庄连通的村庄个数(包括当前村庄) 
				while(ll<rr)
				{
					int mid=ll+rr>>1;
					if(query_interval(1,mid,x)==x-mid+1) rr=mid;
					else ll=mid+1;
				}
				ans+=x-ll+1;
				//二分找右边与当前村庄连通的村庄个数(包括当前村庄)
				ll=x,rr=n;
				while(ll<rr)
				{
					int mid=ll+rr+1>>1;
					if(query_interval(1,x,mid)==mid-x+1) ll=mid;
					else rr=mid-1;
				}
				ans+=ll-x+1;
				printf("%d\n",ans-1);//x这个位置被计算了两次
			}
			else
			{
				if(!tt) continue;//若当前无待修复的村庄就直接执行下一步操作  
				x=st[tt--];
				if(!vis[x]) continue;//可能会重复修复 
				vis[x]=false;//标记为已修复
				update_point(1,x,1);
			}
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值