HDU-1540 Tunnel Warfare 线段树最大连续区间 或 STL巧解

题目大意

n个点,m个操作(1<=n,m<=5e4),m行中每行一个字符c表示操作类型,一个整型数x表示第x个点

D x 表示去掉第x点,Q x表示需输出含 x 的最大连续区间的长度,R x表示还原最后去掉的点

解析

思路很简单,首先,存在的点标1,不存在的标0

对于去掉点非常自然地拿stack存一下

然后线段树应当记录各种最大连续长度

因为是线段树,把问题分治着来看,我们取其中一小部分

比方说就取标号前7个结点

例如对4号结点,如果他含x,那么含x最大连续区间只能是

4的部分 或 4的右部分 + 5的左部分 ①

不可能超过 4的全部 + 5的全部,否则将会在 2 的整个就连续,不会搜到 4

自然,4+5+6甚至+7什么的简直就是天方夜谭

于是 对于 5 号结点 若含 x 就应该是 5 的部分 或 4的右部分 + 5的左部分 ②

根据 ①② 可知我们需记录的是 每个结点的

从左边界开始往右的最大连续长度 rl,从右边界开始往左的最大连续长度 ll,和无条件的最大连续长度 ml

想完这里基本上就解决了,下面看代码 

#pragma GCC optimize(2)
#include 
using namespace std;
struct node
{
	int ll,rl,ml;
}tree[50005*4];
int n,m,x;
char c;
stack sk;
void build(int l,int r,int rt)
{
	tree[rt].ll=tree[rt].rl=tree[rt].ml=r-l+1;
	if(l==r) return;
	build(l,(l+r)/2,rt<<1);
	build((l+r)/2+1,r,rt<<1|1);
}
void update(int x,int c,int l,int r,int rt)
{
	if(xr) return;
	if(l==r) 
	{
		tree[rt].ll=tree[rt].rl=tree[rt].ml=c;
		return;
	}
	update(x,c,l,(l+r)/2,rt<<1);
	update(x,c,(l+r)/2+1,r,rt<<1|1);
	//最大连续长度是 1.左儿子最大长 2.右儿子最大长 3.左儿子右边界+右儿子左边界最大长 之一 
	tree[rt].ml=max(max(tree[rt<<1].ml,tree[rt<<1|1].ml),tree[rt<<1].rl+tree[rt<<1|1].ll);
	tree[rt].ll=tree[rt<<1].ll;//最大左长为左儿子左长,若左儿子左长满区间,则要加右儿子左长 
	if(tree[rt].ll==(l+r)/2-l+1) tree[rt].ll+=tree[rt<<1|1].ll;
	tree[rt].rl=tree[rt<<1|1].rl;//最大右长同理 
	if(tree[rt].rl==r-(l+r)/2) tree[rt].rl+=tree[rt<<1].rl;
}
int query(int x,int l,int r,int rt)
{
	//叶子结点 或 完全连续 或 完全不连续 直接返回 
	if(l==r||tree[rt].ml==r-l+1||!tree[rt].ml) return tree[rt].ml;
	if(x<(l+r)/2+1-tree[rt<<1].rl) return query(x,l,(l+r)/2,rt<<1);//在中间地带左侧 
	if(x>(l+r)/2+tree[rt<<1|1].ll) return query(x,(l+r)/2+1,r,rt<<1|1);//在中间地带右侧 
	return tree[rt<<1].rl+tree[rt<<1|1].ll;//在中间地带直接返回加合 
}
int main()
{
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		build(1,n,1);
		while(m--)
		{
			getchar();
			scanf("%c",&c);
			if(c=='D') scanf("%d",&x),update(x,0,1,n,1),sk.push(x);
			else if(c=='R') update(sk.top(),1,1,n,1),sk.pop();
			else scanf("%d",&x),printf("%d\n",query(x,1,n,1));
		}
	}
	return 0;
}

一般来讲,看到区间更改+查询,自然而然地就想到了线段树

事实上,如果你不知道线段树,想到的必然是下面这个方法

这也是既巧妙又方便又快捷的一个方法,值得学习

法二

找含 x 的最大连续区间,事实上就是找 x 左侧离x最近的删除点 和 x 右侧离 x 最近的删除点

快速删除、添加、查询删除点,那便是红黑树了,set

这也告诉我们,对于 set 也别光想着它去重的功能,也要多想想它作为红黑树的功能

代码十分简单,无需多余的解释,直接看代码吧

#pragma GCC optimize(2)
#include 
using namespace std;
int main()
{
	int n,m,x,c;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		stack sk;
		set st;
		st.insert(0),st.insert(n+1);
		while(m--)
		{
			getchar();
			scanf("%c",&c);
			if(c=='R') st.erase(sk.top()),sk.pop();
			else
			{
				scanf("%d",&x);
				if(c=='D') st.insert(x),sk.push(x);
				else if(st.find(x)!=st.end()) printf("0\n");
				else
				{
					auto l=st.lower_bound(x),r=st.lower_bound(x);
					printf("%d\n",*r-*--l-1);
				}
			}
		}
	}
	return 0;
}

然后简单说下什么呢,预处理插入 0 和 n+1还是很不错的处理的

如果不这么做 还要判 begin end 确实麻烦

另外对语法基础再强调一遍,没找到返回end,end是最后一个元素的后一个

你可能感兴趣的:(HDU-1540 Tunnel Warfare 线段树最大连续区间 或 STL巧解)