1. 程式人生 > >[學習筆記] CDQ分治

[學習筆記] CDQ分治

ans images 兩個 alt 前綴 review lock code tex

最近學了一種叫做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     int
ll=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分治