cdq分治學習筆記

2022-08-04 07:57:13 字數 2962 閱讀 7135

0xff 學習cdq分治的前置知識——分治&歸併排序

分治:分治,字面上的解釋是「分而治之」,就是把乙個複雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題……直到最後子問題可以簡單的直接求解,原問題的解即子問題的解的合併。         

——度娘

歸併排序:歸併排序是普通分治的一種基本應用。將排序序列分為2個子序列,再將2個子序列分別再分為4個子序列,這樣一直分下去,直到子串行長度為1,這個長度為1的子串行即有序子串行。此時可以通過回溯將每個序列的兩個有序子串行通過不斷選取兩個子串行對頭中,小的那乙個入隊的方法,達到排序的目的。由於這樣做相當於遍歷了log(n)遍原序列,所以歸併排序的時間複雜度為o(nlog(n)) .

0x00 cdq分治是什麼?

cdq分治,是由一位國家隊的神仙——陳丹琦在09年的集訓隊**中提出的一種特殊的分治。相對於普通分治而言,cdq分治解決的問題的前後兩部分具有關聯,這種關聯可以影響到最終答案,所以不能像普通分治那樣直接合併兩部分的解。

具體看下圖:

0x10 cdq分治怎麼用&例題

cdq所能解決的問題中,偏序問題是乙個大類。偏序可簡單地理解為有多個相同優先順序的關鍵字的排序。逆序對便是乙個經典的二維偏序問題。對於二維偏序問題,我們常常先用排序解決掉一維,在另一維中使用資料結構(樹狀陣列,線段樹等)來維護。當然,歸併排序也不失為一種好辦法。就以最簡單的逆序對為例:

因為給定序列的前後位置自然有序,所以並不需要用排序解決掉一維。

我們按照順序遍歷給定序列,將每乙個數字加入樹狀陣列中。由於當前數字與它前面的數字若能組成逆序對,它前面的數字應比當前數字大,所以統計當前樹狀陣列中權值比當前數字大的數字的個數,累加進最終答案即可。

ps:由於此題的數字最大為1e9,所以需要離散化。

code:

#include #include #include using namespace std;

int n,a[500001],rk[500001];

struct numberb[500001];

int tot;

long long ans;

struct tree_array

int query(int pos)

void add(int pos,int sum)

}t;bool cmp(number p1,number p2)

for(int i=1;i<=n;i++)

printf("%lld\n",ans);

return 0;

}

解決了二維偏序問題,那麼三維偏序問題要如何解決呢?

當然是用cdq分治

對於第一維,依然用排序解決。

對於第二維,我們用cdq分治:

先分治左區間和右區間,累加左右區間的答案。 然後分別對於左區間和右區間按照第二維關鍵字排序。

有人可能會問,那麼第一維的排序不就亂了嗎?的確如此。但是通過第一維的排序,我們可以保證右區間所有第一維的權值都大於左區間的第一維權值。這樣可以保證只有左區間能影響右區間的答案,而右區間不能影響左區間的答案。

我們再借鑑歸併排序的方法,遍歷右區間,將左區間中所有第二維小於當前數字的第二維的數字的第三維都加入樹狀陣列。然後再以處理二維偏序的方法來統計左區間對右區間的影響,累加進[l,r]的答案中,這個問題就解決了。

code:

#include #include #include using namespace std;

const int n=100001;

const int k=200001;

int n,cnt,tot,k,ansum[n];

struct flowers[n],a[n];

bool cmp_x(flower p1,flower p2)

void add(int w,int sum)

}t;//樹狀陣列

void cdq(int l,int r)

a[i].ans+=t.ask(a[i].z);

}for(int i=l;i0x20 例2 & cdq分治題單

【例2】[cqoi2011]動態逆序對

我們可以根據刪除的順序給每個數字乙個刪除時間$tim$,再根據它在原序列中的位置給定乙個位置$wz$

那麼當前數字i在刪除前能造成的逆序對數即為滿足

$a[i].tima[j].val$

$a[i].tima[j].wz$&&$a[i].val

code:

#include #include #include using namespace std;

int n,m,ti,pos[100005];

long long ansum;

struct numbera[100005];

bool cmp_tim(number p1,number p2)

a[i].ans+=t.ask(a[i].wz-1);

}for(int i=mid+1;i=l;i--)

int main()

for(int i=1;i<=m;i++)

sort(a+1,a+n+1,cmp_tim);

cdq(1,n);

sort(a+1,a+n+1,cmp_tim);

for(int i=1;i<=n;i++)ansum+=a[i].ans;

printf("%lld\n",ansum);

for(int i=1;i【題單】

CDQ分治學習筆記

今天學了一下cdq分治,感覺這東西真的挺好用的,趕緊寫點東西怕以後再忘咯 其實類似於cdq分治的東西在oi早期學排序的時候就應該學過,那就是歸併排序 歸併排序的原理和cdq分治大體一樣,先劃分成兩個區間,遞迴解決兩邊,再合併起來 並且用歸併排序求逆序對的時候本質上就是在解決乙個二維偏序的問題 首先回...

CDQ分治 學習筆記

對於每個 查詢 操作,其結果ans i 1,i 1 ans i 1,i 1 ans i 1,i 1 中所有修改對其造成影響的疊加 這裡的 疊加 需要能夠比較方便的維護,例如sum min max sum min max sum mi n ma x等 定義s ol ve l r solve l,r s...

CDQ分治學習筆記

cdq分治小結 cdq分治,同機房的大佬看了好幾天了,窩這種蒟蒻也來湊個熱鬧 qaq 引用大佬的話 二維裡面 最簡單的簡化版就是逆序對問題了,可以用樹狀陣列來維護,說他是簡化版其實是因為有一維 下標已經有序了,那麼就去大力 搞 另一維就好了 公升級版 一般的二維偏序問題 思路是一樣的,要通過排序使一...