【題目鏈接】
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;
}