前言:
說實話我對於這種沒有固定板子,變化多端的演算法實在是非常頭疼的
但是不學不行,這也是一種很重要的思偉方式
所以趁著這幾天的心情比較好(快要放大周),趕緊學一波~
鳴謝: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 個二元組,每個...