copy from 洛谷P6492
首先,我們爲了方便處理,把 L
抽象成一個數字 ,同理把 R
抽象成 。
數據範圍提示我們,我們需要一個時間複雜度爲 的算法 (或者是 ,但是應該沒有人能把詢問給 掉) 。想到了什麼?對滴,線段樹!
線段樹的一個非常重要的操作就是 pushup
操作,也就是標記合併操作。我們考慮如何實現這個 pushup
操作。
記 爲線段樹上 號節點所表示區間最長的滿足要求的字符串。我們考慮 能取到什麼值:
- 一種比較顯然的是左右兒子的 值的較大值。
- 還有一種就是左兒子的結尾和右兒子的開頭合併,產生了一個新的滿足要求的字符串。
第一個非常好維護,重點在於第二個。如何方便而快捷地求出第二種情況的解呢?
顯然,單獨一個 不能維護第二種情況。我們考慮多引入幾個變量:
- :節點 所表示區間的最左邊的字符。
- :節點 所表示區間的最右邊的字符。
- :節點 所表示區間的最長滿足條件的前綴字符串的長度。
- :節點 所表示區間的最長滿足條件的後綴字符串的長度。
它們有什麼用?顯然,前兩個值可以方便的告訴我們第二種情況是否存在(如果左兒子的 值 右兒子的 值則有第二種情況,否則沒有)。
後兩個呢?也很顯然,當有第二種情況時,第二種情況中的字符串的長度就是左兒子的 右兒子的 值。
到這裏,我們便可以 完成 pushup
操作了。
const int N=2e5+100,M=N<<2;
int sum[M],le[M],ri[M],al[M],ar[M],len[M];
inline void pushup(int o){
if (ri[o<<1]!=le[o<<1|1]){
sum[o]=ar[o<<1]+al[o<<1|1];
sum[o]=max(sum[o],sum[o<<1]);
sum[o]=max(sum[o],sum[o<<1|1]);
}
else sum[o]=max(sum[o<<1],sum[o<<1|1]);
le[o]=le[o<<1];ri[o]=ri[o<<1|1];
if (al[o<<1]==len[o<<1]&&ri[o<<1]!=le[o<<1|1])
al[o]=al[o<<1]+al[o<<1|1];//特殊情況
else al[o]=al[o<<1];//否則直接繼承左兒子
if (ar[o<<1|1]==len[o<<1|1]&&le[o<<1|1]!=ri[o<<1])
ar[o]=ar[o<<1|1]+ar[o<<1];//一切同上
else ar[o]=ar[o<<1|1];//只是變成了右兒子
}//不能理解?不妨畫個圖試試看~
inline void init_node(int o,int t){
sum[o]=al[o]=ar[o]=1;le[o]=ri[o]=t;
}
void build(int o,int l,int r){
len[o]=r-l+1;//區間長度
if (l==r){//到達葉節點
init_node(o,0);
return;
}
register int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
pushup(o);return;
}
void updata(int o,int l,int r,int p){
if (l==r){
init_node(o,!le[o]);
return;
}
register int mid=(l+r)>>1;
if (p<=mid) updata(o<<1,l,mid,p);
else updata(o<<1|1,mid+1,r,p);
pushup(o);return;
}
int main(){
int n=read(),m=read();
build(1,1,n);//建樹
for(int i=1;i<=m;i++){
register int x=read();
updata(1,1,n,x);//修改
printf("%d\n",sum[1]);
}
return 0;
}