本部落格還是從二維偏序開始鋪墊,對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...