詳解CDQ演算法

2021-10-09 22:07:06 字數 3120 閱讀 8466

在解釋cdq演算法之前, 首先回顧演算法中非常經典的分而治之。

divide and conquer(分而治之)

分而治之是電腦科學中非常經典的思想, 通常會把乙個比較複雜的大問題, 分成兩個(或多個)子問題, 分別遞迴分而治之去解決這些子問題, 最後把子問題的結果合併為原問題的解。

有了以上分而治之的思想後, 我們可以看乙個非常經典的演算法例子歸併排序。

歸併排序:

歸併排序有以下幾個步驟

把陣列分成做左區間和右區間

分別對左區間和右區間進行歸併排序

把左區間和右區間的排好序的陣列合併成乙個有序的陣列

歸併排序的複雜度是o(nlogn)

**如下

void merge(vector&arr, vector&tmp, int l, int m, int r) 

while (i <= m)

tmp[p++] = arr[i++];

while (j <= r)

tmp[p++] = arr[j++];

for (i = 0, j = l; i < p; )

arr[j++] = tmp[i++];

}void mergesort(vector&arr, vector&tmp, int l, int r)

}

有了以上的概念後, cdq思想就不難理解了。

在二分排序中, 左區間[l, mid]對右區間[mid + 1, r]是沒有影響的, 但是有些時候, 左區間對右區間有影響, 著這樣的時候, 使用cdq分治能夠很好地解決問題。

cdq分治:

劃分問題為左右區間, [l, mid]和[mid + 1, r]

分別解決左區間和右區間的子問題

計算左區間對右區間的影響

合併左右區間得到原問題的解。

下面看一些cdq演算法實戰的例子吧。

題目描述:

解題思路:

首先, 把逆序對問題轉為成2維的問題, 對於陣列中的每乙個數都可以用二維座標系的座標來表示, (id, number), id代表這個數字是第id個數, number代表這個數字的大小是多少。

比如陣列[1, 5, 3, 4, 2]

就可以表示為(1, 1) (2, 5) (3, 3) (4, 4) (5, 2)

如上圖所示

逆序對的數量=每個點左上角的點數之和

比如在當前圖中, a,b左上角沒有點, c有乙個b, d有乙個b, e有bcd, 因此一共有5個逆序對。

如果是求動態逆序對, 需要在這張圖上動態刪除點, 我們可以把所有新增, 刪除和統計逆序對的操作抽象成兩種操作。

結構體如下

struct oper
id代表這個操作的序號(順序), type代表型別, 是操作1還是操作2, fac代表係數, pos代表要加到哪個答案上。

仔細思考, 可以發現, 只有id小且x和y小的操作1才會對後面id大, x, y大的操作2有影響, 操作1,2內部不會有影響, 因此可以採用分治的思路去解決問題, 按照id分成左右區間。

使用cdq思路, 解決問題的方法如下

把oper按照x, y, id的順序排序

劃分為[l, mid] [mid + 1, r]

按照id二分, 統計左區間對右區間答案的影響

cdq(l, mid) cdq(mid + 1, r)分別解決兩個子問題。

**:

#include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std;

typedef long long ll;

const ll kmaxn = 1e5 + 5;

const ll kmaxm = 5e4 + 5;

ll ans[kmaxm];

ll number[kmaxn], pos[kmaxn];

ll n, m;

struct oper

};oper oper_list[kmaxn * 4], tmp_oper[kmaxn * 4];

ll id_count;

ll tree_arr[kmaxn];

void add(ll x, ll y)

}ll get_sum(ll x)

return ans;

}void read() ;

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

add(i, -1);

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

++id_count;

oper_list[id_count] = ;

++id_count;

oper_list[id_count] = ;

++id_count;

oper_list[id_count] = ;

}}void cdq(ll l, ll r)

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

if (oper_list[i].id <= mid && oper_list[i].type == 1)

add(oper_list[i].y, -oper_list[i].fac);

ll tl = l, tr = mid + 1;

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

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

oper_list[i] = tmp_oper[i];

cdq(l, mid);

cdq(mid + 1, r);}

void solve()

}int main()

玄學演算法CDQ分治

啊剛學這個啊 剛過了一道題就屁顛屁顛來寫部落格很虛啊 bzoj4553 就是這道題 說一下我對cdq分治的理解 我感覺。這個就類似於。把暴力,轉化為。容易優化的暴力。然後優化?並且一般只用於處理問題具有單調性的題,即f i 對任意f j j 不產生影響。例如最長上公升子串行的問題 我們本來需要列舉這...

彙編指令 CDQ

cdq 是乙個讓很多人感到困惑的指令。這個指令把 eax 的第 31 bit 複製到 edx 的每乙個 bit 上。它大多出現在除法運算之前。它實際的作用只是把edx的所有位都設成eax最高位的值。也就是說,當eax 80000000,edx 為00000000 當eax 80000000,edx ...

CDQ分治概述

log l og 的時間把它變成離線問題。正好有些題目的離線問題是比較簡單的。具體是什麼意思呢?我們對於每一層分治,只考慮前一半對於後一半的影響,然後在每個詢問當中記錄下來影響。最後把所有影響合併就可以得到每乙個詢問的答案。舉個例子 區間修改區間查詢。首先,在時間軸上離線分治。每一層分治後把詢問和查...