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是最后一个元素的后一个