cdq分治,傳說中是乙個神犇創造的演算法。
分而治之,將原問題不斷劃分成若干個子問題,直到子問題規模小到足以直接解決
子問題間互相獨立且原問題形式相同,遞迴求解這些子問題,然後將各子問題的解合併得到原問題的解
一般步驟
劃分 divide
將原問題劃分成若干子問題,子問題間互相獨立且與原問題形式相同
解決 conquer
遞迴解決子問題(遞迴是彰顯分治優勢的工具,僅僅進行一次分治策略也許看不出優勢,但遞迴劃分到子問題規模足夠小,子問題的解可用常數時間解決)
合併 merge
將各子問題的解合併得到原問題的解
時間複雜度
直觀估計
分治由以上三部分構成,整體時間複雜度則由這三部分的時間複雜度之和構成
由於遞迴,最終的子問題變得極為簡單,以至於其時間複雜度在整個分治策略上的比重微乎其微。
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)為右上角座標
把每個點的位置變成乙個修改操作,用三元組(時間,橫座標,縱座標)來表示,把每個查詢變成二維字首和的查詢
這樣對於只有位於詢問的左下角的修改,才對詢問有影響
操作的時間是預設有序的,分治過程中按照橫座標從小到大排序,用樹狀陣列維護縱座標的資訊
搜尋 廣度優先搜尋
廣度優先搜尋一層一層地進行遍歷,每層遍歷都是以上一層遍歷的結果作為起點,遍歷乙個距離能訪問到的所有節點。需要注意的是,遍歷過的節點不能再次被遍歷。class solution,int shortestpathbinarymatrix vectorint grid length return 1 cl...
廣度優先搜尋
include include include include using namespace std struct node 圖頂點結構定義 typedef struct node graph 圖形的結構新型態 struct node head 9 圖形頂點陣列 int visited 9 遍歷標...
廣度優先搜尋
廣度優先搜尋詳解 1.也稱寬度優先搜尋,顧名思義,就是將一棵樹一層一層往下搜。演算法首先搜尋和s距離為k的所有頂點,然後再去搜尋和s距離為k l的其他頂點。bfs是一種完備策略,即只要問題有解,它就一定可以找到解。並且,廣度優先搜尋找到的解,還一定是路徑最短的解。但是它盲目性較大,尤其是當目標節點距...