《區間最值操作與歷史最值問題》 - 學習筆記

一隻高二菜逼在省選前兩天發現自己根本不會 segment tree beats ……

3 區間最值操作

直接使用經典做法,在區間中記錄最大值,最大值的個數,次大值即可。

證明一下複雜度。首先找一個好看的方法描述一棵線段樹,吉老師選擇了這樣的方法:

在每個節點維護區間中的最大值作爲 tag ,然後把和父親相等的 tag 刪掉。這樣區間中的次大值就是子樹中的 tag 的最大值。並且顯然還滿足自己的 tag 嚴格大於子樹中的 tag 。

(在真實的線段樹中肯定不會這麼寫,但兩者等價。)

(然後寫的東西和論文不太一樣,我希望我是對的。)

一次區間操作,會先在線段樹上定位到 \(O(\log n)\) 個區間,定位的過程中會把沒有完全覆蓋的區間的 tag 推到沒有 tag 的兒子處。到了對應區間之後更新這裏的 tag 。如果發現此時子樹中存在更大的 tag 那麼就需要暴力 dfs 下去把那些 tag 刪掉。

對於沒有被完全覆蓋的區間,標記下傳是爲了保留這裏的信息(因爲兒子並沒有存),而它 pushup 的時候可能又會把下面的標記搶過來,相當於標記上傳。

定義“標記類”:一次操作給對應區間更新的 tag 爲一類,另外如果一個 tag 被祖先搶了過去那麼仍然認爲它在原來那一類。定義一個標記類的權值爲子樹中存在這一類標記的點數,定義勢能爲所有標記類的權值之和。

一次操作會給若干個區間打上同一個標記類的 tag ,顯然這個新增標記類的權值是 \(O(\log n)\) (普通線段樹複雜度)。

根據定義,一個標記類的 tag 都是相等的。所以暴力 dfs 的時候會把子樹中這一類標記全部清空,所以遍歷了幾個點就讓勢能減小了多少。

標記下傳在一次操作中只會進行 \(O(\log n)\) 次,且只會讓勢能增加 1 。標記上傳是 \(O(1)\) 的,並且不會增加標記類的權值,所以不需要管。

初始勢能也是 \(O(n\log n)\) ,所以總複雜度 \(O((n+m)\log n)\)

做法和前一題完全一樣。繼續分析複雜度。

因爲吉老師分析不出 \(O((n+m)\log n)\) ,所以直接開始胡亂分析。

不再關心標記類(因爲區間加減會使得一個標記類的 tag 不再相等),直接設標記深度和爲勢函數。一次 pushdown 增加 \(O(\log n)\) 的勢能,打一個 tag 也會增加 \(O(\log n)\) 的勢能。

胡亂分析就可以搞出 \(O(m\log^2 n)\)

注意我們幾乎沒有利用區間加減的任何性質,任何一個保持子樹中 tag 位置不變的操作都可以直接丟進去。

3.1 小結

(這和小結有什麼關係)

直接把 min 和 max 的做法合併到一起,分別分析複雜度。它們幾乎互不影響,除非在做區間取 min 的時候直接影響到了區間次大值,但是這種情況要麼會直接往下遞歸,要麼區間中只有兩種值。

然而現在也不能保證標記類的值相等了,所以只能套 \(O(m\log^2 n)\) 的複雜度分析了……

總感覺有很多性質沒有用(這題甚至都沒有區間加減操作,就只有一個上界和一個下界往中間逼),但是又不會用,像極了我做幾何題的樣子(((

3.2 將區間最值操作轉化爲區間加減操作

因爲我們的暴力遞歸操作,所以真正進行的操作只有整體加減和對最值加減。

於是就可以把區間最值的 tag 和整體的 tag 分開維護,好寫很多。

整體加減就給 \(B\) 整體加,最值加減就只給最值的位置加。

因爲同時有對 max 和 min 的操作,可能有神必細節。

1234 操作都是對兩個數組獨立的,所以都可以用上面的方法。現在有整體加減、 \(A\) 最值加減、 \(B\) 最值加減,所以我們需要知道一個位置的 \(A,B\) 是否是最值。所以要維護 4 種信息。

最大值單獨維護,非最大值差分。合併兩個區間的時候可能一邊的最大值變成了整體的非最大值,不過差分並不關心序列的順序具體是怎樣,所以只需要把它接在序列末尾即可。

如果是更加依賴於序列順序的詢問可能就死掉了。

4 歷史最值問題

4.2 可以用懶標記處理的問題

煩死了(

如果沒有區間賦值操作那還比較簡單,只需要維護區間加標記和區間加的歷史最值標記。一旦這個區間被賦值了,那麼以前的區間加標記就廢了,需要維護賦值前後的區間加的歷史最值,和當前是什麼值。

因爲有了神必 \(\max(A_i-x,0)\) 操作,區間中每個值經歷的事情可能不太一樣。維護 tag \((a,b)\) 表示先加 \(a\) 再和 \(b\)\(\max\) 。那麼前三種操作都可以直接用這個 tag 表示。

還是要維護歷史 tag 的最大值,相當於把兩個分段函數的每個位置取 \(\max\) 。發現它的形式竟然沒有變化,於是做完了。複雜度 \(O(m\log n)\)

這東西是不是也可以區間詢問啊?

好像也可以把 1 操作強行轉成區間取 \(\max\) ,然後套吉老師線段樹。

單點修改,區間和的歷史最值。

因爲點之間的時間並不獨立,所以維護每個位置的歷史最值對這題並沒有什麼用。離線下來,把詢問看做點,那麼就變成二維前綴加,單點歷史最值。

可以 kd tree 做。複雜度 \(O(m\sqrt n)\)

4.3 無法用懶標記處理的單點問題

某些區間查詢也被稱爲單點問題,是因爲歷史最小值的區間最小值和單點並無太大區別。

看到區間取 \(\max\) 就知道這題和吉老師或多或少有點聯繫。

直接把 1 操作變成給最小值加正數,然後把最值和非最值分開維護,就做完了。

可以類似例 8 維護分段函數嗎?但是此時不是歷史最大值而是歷史最小值,與 \(\max(A_i,x)\) 方向相反,所以合併分段函數的時候無法維持以前的形式。

4.4 無區間最值操作的區間問題

區間加減,維護區間歷史最值和/歷史版本和。

4.4.1 歷史最小值

最小值的和,與最小值的最小值,本質區別在哪裏?

在之前,我們在一個節點記錄了兩個 tag :當前的加法標記&歷史最小的加法標記。子樹中記錄的 歷史最小值&當前最小值 其實是僅僅考慮子樹中的 tag 之後得到的,我們還需要把它們和這個位置的 tag 合併。

但是因爲以前只考慮最小值,所以維護兩個值並沒有帶來什麼困擾。

現在要求和,那我們不可能維護每個位置的 歷史最小值&當前最小值 ,於是就死掉了。需要尋找新的方法。

注意到當前值一定大於等於歷史最小值,只有一次操作減過頭的時候纔會更新歷史最小值。

\(C_i=A_i-B_i\) 表示這個位置再減多少才能到達歷史最小值,那麼一次區間加 \(x\) 就等價於 \(\max(C_i+x,0)\)

然後區間和就是對 \(C\) 的區間和了。

4.4.2 歷史版本和

因爲一次操作未覆蓋的部分也需要更新歷史版本和,所以考慮設 \(B_i=tA_i+C_i\) ,這裏 \(t\) 表示現在是第幾次操作, \(C_i\) 就表示與當前的 \(A\) 的偏移量。

容易發現一次操作就是對 \(C\) 的區間加減。

4.5 有區間最值操作的區間問題

根據套路,區間最值操作全部變成對最值的加減操作。然後可能該怎麼做就還是怎麼做(

真的嗎?我們發現無區間最值操作的歷史最小值經過轉化之後已經變成區間最值操作了,那麼再套上區間最值操作之後會變成什麼呢?

\(B_i\) 的和轉化爲 \(A,C\) 的區間和。 \(A\) 好做,所以考慮 \(C\) 怎麼做。

關於 \(C\) 有兩種操作:區間加 \(C_i:=\max(C_i+x,0)\) ,或是給區間中 \(A\) 的最小值的位置加正數。

那就分別維護區間中非 \(A\) 最小值位置和最小值位置的 \(C\) 的最小值、最小值個數、次小值個數,然後直接衝。

(這麼一道題要維護多少個 tag 啊……)

衝也衝了,過也過了(?),但是複雜度是啥?

第一種操作只有 \(O(m)\) 種,而第二種操作因爲是加正數所以沒有取 \(\max\) 操作,對複雜度分析沒有太大影響。

但是問題在於 \(A\) 中的最小值的位置在變,所以看起來不是很容易直接分析兩種 \(C\) 中的 tag 個數?

複雜度分析看不懂了,跑了。可能只要帶上信仰就沒有什麼是不能衝的(

最後放上洛谷【模板】線段樹 3 的代碼(常數太大 T 掉了):

using namespace my_std;

int n,m;
ll a[sz];

struct Tag{ll add,pre;Tag(ll A=0,ll P=0){add=A,pre=P;}};
struct Info{int c;ll sum,mx,hmx;Info(int C=0,ll S=0,ll X=-1e17,ll hX=-1e17){c=C,sum=S,mx=X,hmx=hX;}};
Tag merge(Tag x,Tag y){return Tag(x.add+y.add,max(x.pre,x.add+y.pre));}
Info merge(Info x,Tag y){return Info(x.c,x.sum+y.add*x.c,x.mx+y.add,max(x.hmx,x.mx+y.pre));}
Info merge(Info x,Info y){return Info(x.c+y.c,x.sum+y.sum,max(x.mx,y.mx),max(x.hmx,y.hmx));}

Tag tg[sz<<2][2]; Info tr[sz<<2][2];
#define ls k<<1
#define rs k<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
void PutTag(int k,const Tag *t){rep(i,0,1) tg[k][i]=merge(tg[k][i],t[i]),tr[k][i]=merge(tr[k][i],t[i]);}
void pushdown(int k)
{
	tg[0][0]=tg[0][1]=tg[k][0];
	if (tr[ls][1].mx==tr[k][1].mx-tg[k][1].add) PutTag(ls,tg[k]); else PutTag(ls,tg[0]);
	if (tr[rs][1].mx==tr[k][1].mx-tg[k][1].add) PutTag(rs,tg[k]); else PutTag(rs,tg[0]);
	rep(i,0,1) tg[k][i]=Tag();
}
void pushup(int k)
{
	Info L[2]={tr[ls][0],tr[ls][1]},R[2]={tr[rs][0],tr[rs][1]};
	int lmx=L[1].mx,rmx=R[1].mx; if (lmx<rmx) swap(lmx,rmx),swap(L,R);
	if (lmx==rmx) return tr[k][0]=merge(L[0],R[0]),tr[k][1]=merge(L[1],R[1]),void();
	tr[k][1]=L[1]; tr[k][0]=merge(merge(R[0],R[1]),L[0]);
}

void build(int k,int l,int r)
{
	if (l==r) return tr[k][1]=Info(1,a[l],a[l],a[l]),tr[k][0]=Info(0,0,-1e17,-1e17),void();
	int mid=(l+r)>>1;
	build(lson),build(rson);
	pushup(k);
}
void add(int k,int l,int r,int x,int y,ll w)
{
	if (x<=l&&r<=y) return tg[0][0]=tg[0][1]=Tag(w,max(0ll,w)),PutTag(k,tg[0]),void();
	int mid=(l+r)>>1; pushdown(k);
	if (x<=mid) add(lson,x,y,w);
	if (y>mid) add(rson,x,y,w);
	pushup(k);
}
void mdf(int k,int l,int r,int x,int y,ll w)
{
	if (tr[k][1].mx<=w) return;
	int mid=(l+r)>>1; if (l!=r) pushdown(k);
	if (x<=l&&r<=y)
	{
		if (tr[k][0].mx<w) return tg[0][0]=Tag(),tg[0][1]=Tag(w-tr[k][1].mx,0),PutTag(k,tg[0]);
		mdf(lson,x,y,w),mdf(rson,x,y,w),pushup(k); return;
	}
	if (x<=mid) mdf(lson,x,y,w); if (y>mid) mdf(rson,x,y,w);
	pushup(k);
}
Info query(int k,int l,int r,int x,int y)
{
	if (x<=l&&r<=y) return merge(tr[k][0],tr[k][1]);
	int mid=(l+r)>>1; pushdown(k); Info res;
	if (x<=mid) res=merge(res,query(lson,x,y));
	if (y>mid) res=merge(res,query(rson,x,y));
	return res;
}

int main()
{
	file();
	read(n,m);
	rep(i,1,n) read(a[i]);
	build(1,1,n);
	while (m--)
	{
		int tp,l,r,v;
		read(tp,l,r);
		if (tp==1) read(v),add(1,1,n,l,r,v);
		else if (tp==2) read(v),mdf(1,1,n,l,r,v);
		else
		{
			auto ans=query(1,1,n,l,r);
			printf("%lld\n",(tp==3?ans.sum:(tp==4?ans.mx:ans.hmx)));
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章