1. 程式人生 > >cdq分治學習筆記

cdq分治學習筆記

前言

感謝\(\_\_stdcall\)的講解,感謝偉大的導師\(\_tham\)提供一系列練手題

cdq分治是什麼?

國人(陳丹琦)發明的演算法,不同於一般的分治,我們常說的分治是將問題分成互不影響的幾個區間,遞迴進行處理,而所謂\(cdq\)分治,在處理一個區間時,還要計算它對其他區間的貢獻。儘管這已經脫離了分治的基本定義,但為了紀念這位偉大的人物,我們依舊將它稱為分治的一種變體。

二維偏序問題

給定\(n\)個二元組\([a,b]\)\(m\)次詢問,每次給定其中的一個二元組\([c,d]\),求滿足條件\(c<a\&d<b\)的二元組的個數

不知道怎麼做?逆序對你總會求吧?逆序對就是一種經典的二維偏序問題,我們不妨這樣轉換逆序對問題:

給定\(n\)個數,定義一個二元組為\([\)元素下標,元素值\(]\),則共有\(n\)個這樣的二元組

我們只需將約束條件改為:\(c<a\&d>b\)就行了。

那麼,解決二維偏序的一般模式,也只需要改一下合併時的那一句話就好了。

PS:啊?你忘了怎麼用歸併排序求逆序對?戳我

相同的,我們也可以用樹狀陣列來求解。複雜度同樣為\(\Theta(nlogn)\)


既然我們能用樹狀陣列來解決用\(cdq\)分治的題,那我們能不能用\(cdq\)分治來解決樹狀陣列的題目呢?當然可以,比如這道:Luogu3374 樹狀陣列1

給定一個\(n\)個元素的序列\(a\)

,初始值全部為\(0\),對這個序列進行以下兩種操作

操作\(1\):格式為\(1\ x\ k\),把所有位置\(x\)的元素加上\(k\)

操作\(2\):格式為\(2 x y\),求出區間\([x,y]\)內所有元素的和。

這顯然是一道樹狀陣列模板題,考慮如何用\(cdq\)分治來解決它。

我們不妨以修改的時間為第一關鍵字,修改元素的位置為第二關鍵字。由於時間已經有序,我們定義結構體包含\(3\)個元素:\(opt,ind,val\),其中\(ind\)表示操作的位置,\(opt\)\(1\)表示修改,\(val\)表示“加上的值”。而對於查詢,我們用字首和的思想把他分解成兩個操作:\(sum[1,y]-sum[1,x-1]\)

,即分解成兩次字首和的查詢。在合併的過程中,\(opt\)\(2\)表示遇到了一個查詢的左端點\(x-1\),對結果作負貢獻,\(opt\)\(3\)表示遇到了一個查詢的右端點\(y\),對結果作正貢獻,\(val\)表示“是第幾個查詢”。這樣,我們就把每個操作轉換成了帶有附加資訊的有序對(時間,位置),然後對整個序列進行\(cdq\)分治。

#include <cstdio>
#include <cstring>
#include <algorithm>
using std::min;
using std::max;
using std::swap;
using std::sort;
typedef long long ll;

const int N = 5e5 + 10, M = 5e5 + 10;
int n, m, aid, qid;
ll ans[M];
struct Query {
    int ind, opt; ll val;
儲存修改取消

    inline bool operator < (const Query a) const {
        return ind == a.ind ? opt < a.opt : ind < a.ind;
    }
}q[(M << 1) + N], tmp[(M << 1) + N];

inline void cdq (int l, int r) {
    if (l == r) return ;
    int mid = (l + r) >> 1;
    cdq(l, mid), cdq(mid + 1, r);
    int i = l, j = mid + 1, p = l; ll sum = 0;
    while (i <= mid && j <= r)
        if (q[i] < q[j]) {
            if (q[i].opt == 1) sum += q[i].val;
            tmp[p++] = q[i++];
        } else {
            if (q[j].opt == 2) ans[q[j].val] -= sum;
            if (q[j].opt == 3) ans[q[j].val] += sum;
            tmp[p++] = q[j++];
        }
    while (i <= mid) { if (q[i].opt == 1) sum += q[i].val; tmp[p++] = q[i++]; }
    while (j <= r) {
        if (q[j].opt == 2) ans[q[j].val] -= sum;
        if (q[j].opt == 3) ans[q[j].val] += sum;
        tmp[p++] = q[j++];
    }
    for (int k = l; k <= r; ++k) q[k] = tmp[k]; 
}

int main () {
    scanf ("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        q[++qid].ind = i, q[qid].opt = 1;
        scanf("%lld", &q[qid].val);
    }
    int opt, ind, l, r; ll val;
    for (int i = 1; i <= m; ++i) {
        scanf("%d", &opt);
        if (opt == 1) scanf("%d%lld", &ind, &val), q[++qid] = (Query){ind, 1, val};
        else {
            scanf ("%d%d", &l, &r);
            q[++qid] = (Query){l - 1, 2, ++aid}, q[++qid] = (Query){r, 3, aid};
        }
    }
    cdq(1, qid);
    for (int i = 1; i <= aid; ++i)
        printf("%lld\n", ans[i]);
    return 0;
}

三維偏序問題