CDQ分治概述

2021-08-20 15:56:46 字數 4173 閱讀 9884

log

l og

的時間把它變成離線問題。正好有些題目的離線問題是比較簡單的。

具體是什麼意思呢?我們對於每一層分治,只考慮前一半對於後一半的影響,然後在每個詢問當中記錄下來影響。最後把所有影響合併就可以得到每乙個詢問的答案。

舉個例子:區間修改區間查詢。

首先,在時間軸上離線分治。每一層分治後把詢問和查詢都拆成兩個(相當於全部變成修改(查詢)字首和),分別對於數軸從小到大排序。開乙個記錄和的變數,對於排好序的依次修改,查詢也依次查詢,只要保證先處理的操作在數軸上小於後處理的操作。

並且發現這個分治方法特別像歸併排序,正好可以順便處理歸併排序,做到總時間複雜度nl

ogn nlo

gn

當然會問:你為啥不用線段樹呢?

那麼請嘗試一道樹套樹的題:【模板】三維偏序(陌上花開)

雖然可以各種資料結構套資料結構過去,然而親測確實cdq分治要快得多(原諒我線段樹寫得太傻了,速度比較感人)。

具體的cdq方法是什麼呢?

按一維作為時間軸,分治當中第二維排序,第三維使用樹狀陣列維護(每次查詢比第三維小的有多少個)。

可以發現其實這個三維問題只是比二維問題多了乙個樹狀陣列。

所以cdq的好處大概能體會了:使用分治去掉一維,避免使用樹套樹和kdtree。

code:

#include

#include

#include

#include

using

namespace

std;

int b[200005];

int ans[100005],n,k;

bool key;

struct lxy

void insert(int x,int type)

}int ques(int x)

return ret;

}void sovle(int l,int r)

while(p1>l) p1--,insert(a[p1].z,-1);

key=0;sort(a+l,a+mid+1);sort(a+mid+1,a+r+1);

sovle(l,mid);sovle(mid+1,r);

}int main()

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

printf("%d\n",ans[i]);

}

由於當時比較傻,總複雜度是nl

og2 nlo

g2

,所以就沒有寫歸併排序。

其實是比較懶。

好吧,其實我當時不會。

啦啦啦,再看一道題我就下線了[violet 3]天使玩偶

據說是一道kdtree裸題???

然而我們的標題是cdq啊。

一篇比較詳細的部落格

【這道題考慮每個點的周圍四個方向上的點(左下、左上、右下、右上)時不能放在一起討論,不然就沒辦法分治了。所以每個點每次只考慮乙個方向,做四次(趕腳時間會很可怕啊。。。)d=|x-x』|+|y-y』|,當在左下時,d=x+y-(x』+y』),這時只需查詢(x』+y』)最大即可,依然以x為關鍵字排序,以y為下標維護樹狀陣列,每次查詢最大值。 由於左下位置是最好考慮的,那麼我們考慮把另三個方向通過軸對稱變換改變其與當前點的相對位置,將它們搞到左下來再處理】

【其實這道題最大的問題在於如何優化時間,如果按照上兩道題的方法,每次分治到乙個區間就重新把左右區間排序,那麼時間上肯定不允許,畢竟按照這種方法,在每次改變點的相對位置後,還需要把操作的順序歸為初始,這樣就坐等tle吧。。。 實際上,我們可以每次分治前,把整個操作序列按x為關鍵字排序,然後再分治到每一區間時,調整這個區間的操作順序,這樣就可以縮短時間】

大概就是這個思想,對於某些難以比較的東西,我們可以把它拆分成本質相同的東西進行比較。我們也可以發現很有意思的東西:cdq對於區間似乎不是很相容,它可能對於字首的操作更加熟練。當然對於很多題目我們是可以把區間轉化成字首的。

這裡多說一句:這種求最優解劃分成多個最優解再合併的思想實在oi中用的很多的。比如scoi的d2t1:大概是乙個數列,每次詢問連續3個數與

u u

差的絕對值的最大值是多少?支援單點修改。詢問給定

u' role="presentation" style="position: relative;">u

u,求哪3個數符合上述條件。

感覺小學並沒有學好,絕對值的另一種表示方法:ma

x(u−

x,x−

u)m ax

(u−x

,x−u

)$

我們只需要建8棵線段樹分別維護:+a

+b+c

+ a+

b+

c−a+

b+c −a+

b+

c+a−

b+c +a−

b+

c+a+

b−c +a+

b−

c+a−

b−c +a−

b−

c−a+

b−c −a+

b−

c−a−

b+c −a−

b+

c−a−

b−c −a−

b−

c然後在這裡面分別取最大值,再加上對應的

u u

的個數再取ma

x' role="presentation" style="position: relative;">max

max。

由於取絕對值和的情況在8種裡面是最大的。所以不合法情況不會成為最優解。另一種說法,如果乙個不合法情況成為最優解,一定會有這種情況對應的合法情況比它更優。而且這種做法包含了所有合法情況,不會出現漏解。

當然,今天我們並不是講這道題,只是突然想起來了。

如果不理解天使玩偶的話,看一看**,詳細解釋一下:

#include

#include

#include

#include

#include

#define lowbit(x) (x&(-x))

using namespace std;

bool key=0;

struct lxy

return a;

}inline int query(int

x)//樹狀陣列求最值

return ret;

}inline void modify(int

x,int k)//樹狀陣列修改

}void cdq(int l,int r)//cdq分治

int mid=(l+r)>>1;

cdq(l,mid);cdq(mid+1,r);//先向下分治

int t1=l,t2=mid+1;

for(int i=l;i<=r;i++)

else

}for(int i=l;i<=r;i++)

q[i]=tax[i];

for(int i=l;i<=r;i++)

while(!d.empty())

b[d.top()]=-0x3f3f3f3f,d.pop();//清空樹狀陣列

}int main()

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

maxx++;maxy++;

for(int i=1;i<=maxx+maxy;i++)

b[i]=-0x3f3f3f3f;

//做4遍,分別對應查詢點的4個方位

for(int i=1;i<=cnt;i++) q[i].x=maxx-q[i].x;cdq(1,cnt);

key=0;sort(q+1,q+1+cnt);

for(int i=1;i<=cnt;i++) q[i].y=maxy-q[i].y;cdq(1,cnt);

key=0;sort(q+1,q+1+cnt);

for(int i=1;i<=cnt;i++) q[i].x=maxx-q[i].x;cdq(1,cnt);

key=0;sort(q+1,q+1+cnt);

for(int i=1;i<=cnt;i++) q[i].y=maxy-q[i].y;cdq(1,cnt);

key=0;sort(q+1,q+1+cnt);

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

if(q[i].type==2)

printf("%d\n",q[i].ans);

}

CDQ分治總結

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

cdq分治小結

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

cdq(時間分治)

explorer 題意 給定n個點m條無向邊,每條邊有乙個寬度 l,r 現在有乙個人在一號節點,這個人的寬度不知,問最後這個人 到達n號節點可以有多少種大小。通過一條邊的條件是這個人的寬度大小在這條邊的範圍之內。用詞不準確,但大概題意就是這樣tttt。菜鳥一看見這道題,這不dfs搜尋一下就ok了,菜...