这题目典型的线段树做法,但是网上很多人对每个节点都记录了3个值,左端起最长,右端起最长和中间最长,实际没有必要。
因为这题本质上我理解为是求所在序列的长度,举例,n为7,d 3 d 5,那么4所在的长度为1,1,2,3所在的长度为3。谈不上最大最小。只需要维护左端起最长的连续区间和右端起最长的连续区间即可。
对于查询,Q k,如果k在左儿子的右区间内并且k在右儿子的左区间内,那么就返回这两个区间的和,否则递归查询子区间。
而更新节点是递归返回时从底向上更新。
而找到R操作的点很简单,对于每个D操作就进栈,然后R操作就直接出栈顶即可。
还有一点,可能会有对重复点的删除和恢复,设置一个bool数组表示是否摧毁过该点即可。
代码不是很快,265ms:
#include<cstdio>
#include<cstring>
const int maxn = 5e4+9;
struct A{
int ml,mr;
}stree[maxn<<2];
bool vi[maxn];
int stack[maxn];
int left,righ,k,q,top;
void buildTree(int rt,int l,int r){
stree[rt].ml=stree[rt].mr=r-l+1;
if(r>l){
int mid=(l+r)>>1;
buildTree(rt<<1,l,mid);
buildTree(rt<<1|1,mid+1,r);
}
}
inline void pushup(int rt,int l,int mid,int r){
stree[rt].mr=stree[rt<<1].mr;
stree[rt].ml=stree[rt<<1|1].ml;
if(stree[rt].mr==mid-l+1)stree[rt].mr+=stree[rt<<1|1].mr;
if(stree[rt].ml==r-mid)stree[rt].ml+=stree[rt<<1].ml;
}
void update(int rt,int l,int r,int v){
int mid=(l+r)>>1;
if(r>l){
if(k<=mid)update(rt<<1,l,mid,v);
else update(rt<<1|1,mid+1,r,v);
pushup(rt,l,mid,r);
}else{
stree[rt].ml=stree[rt].mr=v;
}
}
int query(int rt,int l,int r){
int mid=(l+r)>>1,ans=0;
if(l<r){
if(k>=mid+1-stree[rt<<1].ml&&k<=mid+stree[rt<<1|1].mr)
ans=stree[rt<<1].ml+stree[rt<<1|1].mr;
else{
if(k<=mid)ans=query(rt<<1,l,mid);
else ans=query(rt<<1|1,mid+1,r);
}
}
return ans;
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)==2){
top=0;
memset(vi,0,sizeof(vi));
buildTree(1,1,n);
char s[2];
while(m--){
scanf("%s",s);
if(*s=='D'){
scanf("%d",&k);
stack[top++]=k;
if(!vi[k]){
update(1,1,n,0);
vi[k]=true;
}
}else if(*s=='R'){
k=stack[--top];
if(vi[k]){
update(1,1,n,1);
vi[k]=false;
}
}else{
scanf("%d",&k);
printf("%d\n",query(1,1,n));
}
}
}
return 0;
}