cdq分治 整體二分 學習筆記

2022-09-24 01:51:10 字數 3229 閱讀 9087

本部落格還是從二維偏序開始鋪墊,對cdq分治進行講解(實際上是給自己講,因為沒人看)。

前置知識:歸併排序

cdq分治的學習需要保證對歸併排序的理解,雖然它是乙個基礎演算法。

給定 \(n\) 個元素,第 \(i\) 個元素有兩個屬性 \(a_i\) 和 \(b_i\) ,設 \(f(i)\) 為滿足 \(a_j \leq a_i,b_j \leq b_i,j \neq i\) 的 \(j\) 的個數,對於每乙個 \(0 \leq d \leq n - 1\) ,求滿足 \(f(i) = d\) 的 \(i\) 的個數。

我們可以發現,對於乙個元素 \(i\) ,只有滿足 \(a_j \leq a_i\) 的元素才有可能對 \(f(i)\) 產生貢獻。

不就是要單個屬性小於等於嗎,太簡單了!我們只需要對屬性 \(a\) 單獨排序即可!

那麼屬性 \(b\) 呢?注意到對 \(f(i)\) 有貢獻的元素要滿足 \(b_j \leq b_i\) ,那麼在排序後,我們需要獲取在某個元素前面的滿足 \(b_j \leq b_i\) 的個數,這一部分顯然使用值域 bit ,按順序跑元素,然後乙個乙個加入,然後求字首和即可。

有乙個問題,如果兩個元素的 \(a\) 相同,然而 \(b\) 小一點的排到了後面,會發生什麼?

這乙個的貢獻算不到了

所以,在 \(a\) 相同的元素內,我們應該對 \(b\) 排序。

所以 cmp 函式長這個樣子:

bool cmp(node x,node y)
到目前為止,我相信能來學 cdq 分治的大佬肯定能理解二維偏序,那麼就進入我們的正題了:

p3810

在二維偏序中,我們在滿足 \(a\) 有序的情況下,對 \(b\) 使用值域 bit 得到了答案。

現在三維偏序多了乙個屬性,我們無法在一次排序內對兩個屬性進行排序,怎麼辦呢...

對新進來的屬性進行歸併排序

假設我們一開始排序 \(a\) ,對 \(b\) 跑歸併,對 \(c\) 用值域 bit。

在歸併排序的過程中,分析左邊區間對右邊區間的貢獻。

為什麼可以這樣算?

由於歸併排序是把原來的陣列拆成一半一半的小區間,在拆的過程中我們未曾改變順序,所以第一次開始算的時候,\(a\) 是有序的!

然後,我們合併區間上去,由於我們之前只是在小區間內合併,對於右邊的大區間,我們的 \(a\) 仍然是有序的!

所以,我們可以直接分析左邊區間對右邊區間的貢獻了(右邊區間內部自己的貢獻,和左邊區間內部自己的貢獻並不在考慮範圍內,這部分會在之前更小的區間的歸併排序過程中計算出來)。

這部分長這個樣子。

void solve(int l,int r)

else

} while(posl <= mid)

while(posr <= r)

for(int i = l;i <= mid;i ++)t.update(e[i].c,-e[i].cnt);

for(int i = l;i <= r;i ++)e[i] = tmp[i];

}

解釋一下,這個題有重點,這個 cnt 就是與這個點相同的點的個數。

f 就是這個元素的函式值。

在「並」的過程中,我們保證了 \(b\) 一定是排序好的,既然如此,我們就可以對 \(c\) 用值域 bit 了。對於左邊的區間,進行乙個值域樹狀陣列的加,對於右邊的區間,直接在值域 bit 中屬性 \(c\) 小於它的個數,值域 bit 裡面只有左區間的元素,所以這就是左區間對右區間的貢獻。

這個題就結束了。

由於 cdq分治 只能一次性處理完,所以跑 cdq分治 需要如下條件:

修改操作對詢問的貢獻獨立,修改操作相互不影響

題目可以離線處理

cdq分治(在這個題中)實際上幫助我們做到的是通過對時間複雜度增加乙個log來降維。

那麼,跟上面講解一樣的,在cdq分治中,對於劃分出來的兩個區間,前乙個子問題需要用來解決後乙個子問題。

想必到這裡,不會有人不懂了吧(

不懂歡迎問我。

貼上完整**。

#include#define int long long

#define mem(x,y) memset(x,y,sizeof(x))

#define frein freopen("in.in","r",stdin)

#define freout freopen("out.out","w",stdout)

#define debug(x) cout << (#x) << " = " << x << endl;

using namespace std;

int read()

while(ch >= '0' && ch <= '9')s = s * 10 + ch - '0',ch = getchar();

return s * w;

}int maxm;

int lowbit(int x)

struct bit

int query(int i)

}t;struct nodee[100010],tmp[100010];

int n;

int ans[100010];

int cnt = 1;

bool cmp(node x,node y)

void solve(int l,int r)

else

} while(posl <= mid)

while(posr <= r)

for(int i = l;i <= mid;i ++)t.update(e[i].c,-e[i].cnt);

for(int i = l;i <= r;i ++)e[i] = tmp[i];

}signed main()

sort(e + 1,e + n + 1,cmp);

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

solve(1,cnt);

for(int i = 1;i <= cnt;i ++)ans[e[i].val + e[i].cnt - 1] += e[i].cnt;

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

return 0;

}

這個部落格還沒完,還會有一些簡單例題。

關於整體二分:

請叫我填坑。

2022.2.23 13:24

CDQ分治 整體二分

ps 2683 1176是雙倍經驗題 題意 一種操作一種詢問 1,x,y,a 表示將 x y 點值加上a 2,x1,y 1,x2 y2 表示詢問以 x 1,y1 為左上角 x 2,y2 為右下角的矩陣內點和。題意 給定一堆花,每個花有三個屬性,定義一朵花比另一朵花美麗當期僅當三個值都大於等於另一朵花...

CDQ分治 整體二分

cdq分治本質就是分兩半,分別計算兩邊區間的貢獻,然後再考慮跨區間的貢獻。具體教程網上一搜一大把 題單 51nod 1376 考慮用 f i 記錄以i結尾的最長上公升子串行的長度 個數,然後每次切兩半,先計算 l,mid 的答案,然後按照原陣列a的值進行排序,從前往後掃,如果下標在前一半區間則更新乙...

離線分治 整體二分與CDQ分治

這兩個演算法都是離線的分治演算法。其中cdq分治是基於時間的分治演算法。整體二分是基於值域的分治演算法。先講講整體二分吧。我們拿 zjoi2013 k大數查詢作為例子。一 原理 將所有的修改和查詢操作離線存下來。每次二分所有修改和詢問操作,分成兩部分解決。二 每個子問題 slove front,la...