【题目链接】
http://acm.hdu.edu.cn/showproblem.php?pid=1540
【解题报告】
题目大意:
给长度为N的序列,有三种操作:
1.删除某个点a
2.查询包括点a在内的最长连续区间
3.恢复最后一个被删除的点
这道题目和hotel很像,不同的是,hotel区间更新,这里单点更新;在区间查询上,也不尽相同。
在我做这道题目的过程中,唯一的难点恰恰在如何查询最长区间上。
对线段树每个节点维护两个变量:pre和suf,分别表示该区间最长前缀和最长后缀。
那么我们对点x的查询化为对1..x区间和对x..n区间的查询,分别查询1..x的最长后缀和x..n的最长前缀。
对于最长后缀,在查询到某一个节点的时候无非是三种情况:
1.全部落在右子树里,在右子树里查询
2.全部落在左子树里,在左子树里查询
3.横跨左子树和右子树,那么如果最长后缀填满了查询区间在右子树的部分,那么把左子树的部分也算进去。
求最长前缀方法相同。
因此我们看到这样的一个分类讨论的思想在不同的题目里都有所运用。可以把一个看起来复杂的查询转化成结构清晰的分治算法。
【参考代码】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;
const int maxn=50000+50;
struct Node
{
int pre,suf,sub;
void set( int pre=0,int sub=0,int suf=0 )
{
this->pre=pre; this->sub=sub; this->suf=suf;
}
};
Node tree[maxn*4];
int del[maxn];
int N,M;
void build( int O, int L, int R )
{
if( L==R ) tree[O].set( 1,1,1 );
else
{
int mid=(L+R)/2;
build( O*2,L,mid );
build( O*2+1, mid+1, R );
tree[O].set( R-L+1,R-L+1,R-L+1 );
}
// cout<<L<<" "<<R<<" "<<tree[O].pre<<" "<<tree[O].suf<<endl;
}
void maintain( int O, int L, int R )
{
int lc=O*2,rc=O*2+1,mid=(L+R)/2;
if( L<R )
{
tree[O].sub=max( max( tree[lc].sub,tree[rc].sub ), tree[lc].suf+tree[rc].pre );
tree[O].pre=tree[lc].pre;
tree[O].suf=tree[rc].suf;
if( tree[O].pre==mid-L+1 )tree[O].pre+=tree[rc].pre;
if( tree[O].suf==R-mid) tree[O].suf+=tree[lc].suf;
}
}
void update( int O, int L, int R, int x, int op ) //0表示删除,1表示恢复
{
if( L==R )
{
if( op==0 ) tree[O].set( 0,0,0 );
else tree[O].set( 1,1,1 );
return;
}
int mid=(L+R)/2;
if( x<=mid ) update( O*2,L,mid,x,op );
else update( O*2+1, mid+1, R, x,op );
maintain( O,L,R );
}
int query( int O, int L, int R, int qL, int qR, int op ) //0表示qL..qR右边连续区间,1表示qL..qR左边连续区间
{
if( qL<=L && R<=qR ) return op? tree[O].pre : tree[O].suf;
int mid=(L+R)/2;
if( qR<=mid )return query( O*2,L,mid,qL,qR,op );
else if( qL>mid ) return query( O*2+1,mid+1,R,qL,qR,op );
else
{
int lsum=query( O*2,L,mid,qL,qR,op );
int rsum=query(O*2+1,mid+1,R,qL,qR,op);
if( op==0 && rsum==qR-mid )return rsum+lsum;
if( op==1 && lsum==mid-qL+1)return rsum+lsum;
if( op )return lsum; else return rsum;
}
}
int main()
{
while( ~scanf("%d%d",&N,&M) )
{
build( 1,1,N );
memset( del,0,sizeof del );
stack<int>s;
while(M--)
{
char str[2]; scanf("%s",&str);
if( str[0]=='R' )
{
while( !s.empty() && !del[ s.top() ] )s.pop(); //栈顶元素已经恢复了,就弹出来
if( !s.empty() )
{
update( 1,1,N, s.top(),1 ); // 0表示炸毁,1表示还原
del[s.top()]=0; //恢复它
s.pop(); //从栈里弹出来
}
}
else if( str[0]=='D' )
{
int x; scanf("%d",&x);
update( 1,1,N,x,0 );
del[x]=1; //删除了要打标记
s.push(x); //删除了要推入栈
}
else
{
int x; scanf( "%d",&x );
int temp=query( 1,1,N,1,x,0 ) + query( 1,1,N,x,N,1 );
if(temp)temp-=1;
printf( "%d\n",temp );
}
}
}
return 0;
}