CDQ分治 分治(真得頭疼)

2021-08-13 10:31:26 字數 3874 閱讀 1901

前言:

說實話我對於這種沒有固定板子,變化多端的演算法實在是非常頭疼的

但是不學不行,這也是一種很重要的思偉方式

所以趁著這幾天的心情比較好(快要放大周),趕緊學一波~

鳴謝:tham,stdcall

時間複雜度

分治的乙個經典例子就是歸併求逆序對

簡單敘述一下演算法:

利用歸併把序列對半分,在一般的歸併過程中,我們在前後兩部分各設兩個指標,按照大小順序合併成乙個序列

我們要做的就是在這個過程中記錄逆序對個數

什麼情況下會有逆序對呢?

無非是在前面的數比在後面的數大(翻譯過來:在前半部分的數比在後半部分的數大)

設前半部分的指標為t1,後半部分的指標為t2

如果a[t1] > a[t2],那麼t1~mid的元素一定都比t2大,一定都可以與t2形成逆序對

我為什麼要介紹這個呢?

因為歸併就是乙個最簡單的分治問題

我們在合併兩個子區間的時候,要考慮到左邊區間的對右邊區間的影響

即,我們每次從右邊區間的有序序列中取出乙個元素的時候,要把「以這個元素結尾的逆序對的個數」(左邊區間有多少個元素比他大)

這是乙個典型的cdq分治的過程

cdq分治是我們處理各類問題的重要**

它的優勢在於可以頂替複雜的高階資料結構,而且常數比較小

缺點在於必須離線操作

二維偏序問題

上面介紹了歸併求逆序對的經典問題,我們由此引入二維偏序問題:

給定n個有序對(a,b),求對於每個(a,b),滿足a0 < a且b0 < b的有序對(a0,b0)有多少個

在歸併求逆序對的時候,實際上每個元素是用乙個有序對(a,b)表示的,

其中a表示陣列中的位置,b表示該位置對應的值

我們求的就是「對於每個有序對(a,b),有多少個有序對(a0,b0)滿足a0 < a且b0 > b」,這就是乙個二維偏序問題

注意到在求逆序對的問題中,a元素是預設有序的,即我們拿到元素的時候,陣列中的元素是預設從第乙個到最後乙個按順序排列的,所以我們才能在合併子問題的時候忽略a元素帶來的影響

因為我們在合併兩個子問題的過程中,左邊區間的元素一定出現在右邊區間的元素之前,即左邊區間的元素的a都小於右邊區間元素的a

那麼對於二維偏序問題,我們在拿到所有有序對(a,b)的時候,先把a元素從小到大排序

這時候問題就變成了「求順序對」,因為a元素已經有序,可以忽略a元素帶來的影響,和「求逆序對」的問題是一樣的。

考慮二維偏序問題的另一種解法,用樹狀陣列代替cdq分治,即常用的用樹狀陣列求順序對

在按照a元素排序之後,我們對於整個序列從左到右掃瞄,每次掃瞄到乙個有序對,求出「掃瞄過的有序對中,有多少個有序對的b值小於當前b值」

然而當b的值非常大的時候,空間和時間上就會吃不消,便可以用cdq分治代替,就是我們所說的「頂替複雜的高階資料結構」

二維偏序問題的拓展

給定乙個n個元素的序列a,初始值全部為0,對這個序列進行以下兩種操作

操作1:格式1 x k,把位置x的元素加上k

操作2:格式為2 x y,求出區間[x,y]內所有元素的和

這是乙個經典的樹狀陣列問題

但是我們就是要沒事找事,我們用cdq分治解決它——帶修改和詢問的問題

我們把ta轉化成乙個二維偏序問題,每個操作用乙個有序對(a,b)表示,其中a表示操作的時間,b表示操作的位置,時間是預設有序的,所以我們在合併子問題的過程中,就按照b從小到大的順序合併。

首先我們把原數列和1操作都看作是修改操作

詢問操作[l,r]我們拆成兩個:l-1,r

因為我們詢問的是乙個區間和,一般的思路就是字首和相減(我們需要具備這樣的思維)

實際上我們這道題也可以這樣,我們按照時間順序進行修改

記錄字首和,當遇到l-1的標記時,我們減去sum(l-1)

遇到r標記時,詢問的處理就完成了

具體流程:

需要注意的是:

#include

#include

#include

#define ll long long

using

namespace

std;

const

int n=5000010;

int n,m,totx=0,tot=0; //totx是操作的個數,tot詢問的編號

struct node

else

//只統計右邊區間內的查詢結果

}for (int i=l;i<=r;i++) a[i]=b[i];

}int main()

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

}cdq(1,tot);

for (int i=1;i<=totx;i++) printf("%lld\n",ans[i]);

return

0;}

三維偏序問題給定n個有序三元組(a,b,c),求對於每個三元組(a,b,c),有多少個三元組(a0,b0,c0)滿足a0 < a且b0 < b且c0 < c不用cdq的演算法,我們就不說了(太麻煩了)

類似二維偏序問題,先按照a元素從小到大排序,這樣我們就可以忽略a元素的影響

然後cdq分治,按照b元素從小到大進行歸併排序

哪c元素我們要怎麼處理呢?

這時候比較好的方案就是借助權值樹狀陣列,

每次從左邊取出三元組(a,b,c),根據c值在樹狀陣列中進行修改

從右邊的序列中取出三元組(a,b,c)時,在樹狀陣列中查詢c值小於(a,b,c)的三元組的個數

注意,每次使用完樹狀陣列要把樹狀陣列清零

三維偏序問題的拓展

平面上有n個點,每個點的橫縱座標在[0,1e7]之間,有m個詢問,每個詢問為查詢在指定矩形之內有多少個點,矩形用(x1,y1,x2,y2)的方式給出,其中(x1,y1)為左下角座標,(x2,y2)為右上角座標

把每個點的位置變成乙個修改操作,用三元組(時間,橫座標,縱座標)來表示,把每個查詢變成二維字首和的查詢

這樣對於只有位於詢問的左下角的修改,才對詢問有影響

操作的時間是預設有序的,分治過程中按照橫座標從小到大排序,用樹狀陣列維護縱座標的資訊

CDQ分治概述

log l og 的時間把它變成離線問題。正好有些題目的離線問題是比較簡單的。具體是什麼意思呢?我們對於每一層分治,只考慮前一半對於後一半的影響,然後在每個詢問當中記錄下來影響。最後把所有影響合併就可以得到每乙個詢問的答案。舉個例子 區間修改區間查詢。首先,在時間軸上離線分治。每一層分治後把詢問和查...

CDQ分治總結

cdq這個東西嘛,說容易其實也很容易,說難其實也有些難,但只要細細品味,定能發現其中的真理的!那真理,也會像蝴蝶一般,破蛹而出,化身為一道亮麗的風景線。題記。咳咳,閒話就講到這裡了,切入正題。首先我們來了解一下cdq分治這個東東。cdq分治,他的常數小,但必須離線操作the most importa...

cdq分治小結

一般的分治,眾所周知的,是通過將大的問題拆小,然後對小問題的答案進行合併得到大問題的答案,但是cdq分治不是。我們知道,分治時,將乙個區間從中間斬開,分兩半處理,cdq分治在處理完之後,不是合併答案,而是計算左區間對右區間的貢獻,這樣子可以將維度降低,問題就更好做了。現在有 n nn 個二元組,每個...