[學習筆記] CDQ分治
最近學了一種叫做CDQ分治的東西...用於離線處理一系列操作與查詢似乎跑得很快233
CDQ的名稱似乎源於金牌選手陳丹琦
概述:
對於一坨操作和詢問,分成兩半,單獨處理左半邊和處理左半邊對於右半邊的影響,就叫$CDQ$分治。
乍一看似乎不算難理解...?
這"一坨操作和詢問"是要求靠左的操作可以影響所有右側操作,靠右的查詢的值依賴於左側的操作...
內部實現:
將左右區間按一定規律排序後分開處理,遞歸到底時直接計算答案,對於一個區間,按照第二關鍵字split成兩個區間,先處理左區間,之後因為整個區間是有序的,就可以根據左區間來推算右區間的答案,最後遞歸處理右區間即可。歸納起來就是
1. 區間按第一關鍵字分成兩半
2. 計算左區間對右區間的貢獻
3. 撤銷修改
4. 按第二關鍵字拆成兩半並排序
5. 遞歸下去繼續處理
這種時候常常只能選擇參考代碼感性理解一下(霧)
例題-Mokia
對於上面的例題($Mokia$)來說,我們先將一個查詢操作差分為$4$個前綴和分別計算,然後將所有操作按$x$值為第一關鍵字,$y$為第二關鍵字排序(保證左邊的操作可以影響右邊操作),然後按照時間戳分開重新排序為兩個序列並分開遞歸下去求值QwQ
直接拿這個板子題的實現細節舉個栗子好了QwQ
我們拿$CDQ$函數來說一下...
1 void CDQ(int l,int r){ 2 if(l==r) 3 return; 4 int mid=(l+r)>>1; 5 intll=l; 6 int lr=mid+1; 7 for(int i=l;i<=r;i++){ 8 if(query[i].ID<=mid&&query[i].operation==0) 9 Add(query[i].y,query[i].value); 10 if(query[i].ID>mid&&query[i].operation==1) 11 ans[query[i].position]+=query[i].value*Query(query[i].y);12 } 13 for(int i=l;i<=r;i++){ 14 if(query[i].ID<=mid&&query[i].operation==0) 15 Add(query[i].y,-query[i].value); 16 } 17 for(int i=l;i<=r;i++){ 18 if(query[i].ID<=mid) 19 tmp[ll++]=query[i]; 20 else 21 tmp[lr++]=query[i]; 22 } 23 for(int i=l;i<=r;i++) 24 query[i]=tmp[i]; 25 CDQ(l,mid); 26 CDQ(mid+1,r); 27 }
$2\text{~}6$行沒啥可講的(吧)
然後我們從$l$到$r$進行遍歷.因為我們按$x$和$y$排序了所以更改肯定在它所影響的查詢操作的值計算之前進行($7\text{~}12$行)
接著我們撤銷修改,因為遞歸下去後這些值會失效所以要清空($13\text{~}16$行)
然後我們按照時間戳分別分為左右兩部分($17\text{~}24$行)
最後遞歸下去處理.
仔細分析我們可以發現這個過程剛好做到了"不重不漏":不重復計算查詢涉及到的修改操作的貢獻;不漏掉修改操作對後面查詢的貢獻.
然後摘一句總結
CDQ分治主要用於處理能夠離線的低維偏序(3維及以下),3維偏序常態是套BIT,一般對於x排序,然後對於id進行cdq分治,對於y用BIT來維護即可。
據說更高維的偏序也可以處理但是好像需要一些特殊方法?
(然後又草率地結束了)
(我好菜啊QwQ)
(放圖跑)
[學習筆記] CDQ分治