CDQ分治(初步入門)

2022-09-20 20:57:15 字數 2836 閱讀 6624

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)為右上角座標

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

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

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

Linux初步入門

對於linux的初步入門以前都要對計算機概論有一些了解。1.計算機 接收使用者輸入指令與資料,經過 處理器的資料與邏輯單元運算處理後,以產生或儲存成有用的資訊。2.計算機五大硬體 輸入單元 輸出單元 cpu內部的控制單元 算術邏輯單元與記憶體五大部分。3.cpu種類 精簡指令集 risc 與複雜指令...

python初步入門

help obj 檢視幫助 import 檔名 匯入乙個檔案 from 檔名 import 方法名 匯入檔案中的乙個方法 dir var 檢視變數屬性和方法 none 空物件 邏輯運算 and or not elif elseif 沒有switch語句 沒有三目運算子 for迴圈 for 變數 in...

Treap初步入門

樹堆,在資料結構中也稱 rm treap 是指有乙個隨機附加域滿足堆的性質的二叉搜尋樹,其結構相當於以隨機資料插入的二叉搜尋樹。其基本操作的期望時間複雜度為 rm o logn 相對於其他的平衡二叉搜尋樹,rm treap 的特點是實現簡單,且能基本實現隨機平衡的結構。rm q 為什麼要用 rm t...