CDQ分治 学习笔记

我们先回顾一下逆序对问题:

P1908 逆序对

在本题中,我们需要求所有 i<jai>aj 的对数。我们一般有两种方法:一种是树状数组求解,一种是归并求解。其实,令 ai=ibi= 输入的数字,本题实际上就是求满足 ai<ajbi>bj 的对数,这是一道典型的二维偏序问题,只不过默认了 ai=i

想一下我们是怎么求解的:我们一个一个地把 bi 插入到树状数组里,设当前插入到了第 i 个位置,实际上就是满足了 aj<ai。此时我们再利用树状数组求所有已插入的 bj 中大于 bi 的数量,由于前提已经满足了 aj<ai,所以此时所有 bj>bi 一定都满足条件。反过来,若是要求所有 ai<aj,bi<bj 的对数(即顺序对),只需在统计答案时稍作改变即可。

但如果我们需要求关于 ai<aj,bi<bj,ci<cj 的问题呢?这便是 CDQ 分治能解决的经典问题,即三维偏序问题。

P3810 【模板】三维偏序(陌上花开)

题意:设 fi 表示 j=1,jin[ajai,bjbi,cjci],对于每个 d[0,n),我们需要求出 fi=d 的数量。

我们运用刚才的思想,我们首先以 ai 为第一关键字,bi为第二关键字,ci为第三关键字升序排序。注意到若对于两个不同的 i,三个数有可能完全相同,我们需要额外处理一下,稍后再谈。下设 cnti 表示 (ai,bi,ci) 出现的次数,ansi 表示第 i 个数对的答案。显然地,此时对于每个 i,对 fi 的贡献只可能来自于 j<i

我们考虑归并:对于一个区间 [l,r],我们先分别计算 [l,mid][mid+1,r] 中可能产生的贡献。此时两个区间都是按照 bi 升序排序的,不一定按照 ai 了,因为无论左边和右边的 ai 多大,由于前面已经排序过了,所以只要左边的 bici 满足条件,就一定可以对右边产生贡献,而不用考虑 ai。而原本的 ai 只对原本的小区间有影响,但原本的小区间里产生的贡献已经算过了,所以此时的 ai 便不重要了。然后,由于只有 [l,mid] 可能对 [mid+1,r] 产生贡献,我们按照 bi 像归并排序一样升序排序,但要额外统计:

  • blptrbrptr 时,我们把 clptr 在树状数组中加上 cntlptr(显然地,一个对数合法了,相同的也一定合法),并增加 lptr;
  • blptr>brptr 时,我们对 ansrptr 加上树状数组中小于 crptr 的前缀和。由于对 bi 进行排序,所以此时加上的一定是合法的数量。

排序完后,我们需要再将原本的树状数组每个 climid 中减去对应的 cnti,这是为了消除这个区间的影响,因为归并到大区间及求之后的小区间还需要用到这个树状数组,若不减去则会重复计算。

对整个区间归并后,每个 ansi 就是对第 i 个数对不算入这个数对的重复而有算入其他数对的重复的答案了。于是,对于每个 ansi,应该对 fansi+cnti1 加上 cnti

不清楚的可以看代码理解。

AC Code:

#include <bits/stdc++.h>
#define lowbit(x) ((x) & -(x))
#define mid (l + r >> 1)

using namespace std;

struct P {
    int a, b, c, id, cnt;
    
    const bool operator<(const P &rhs) const {
        if (a != rhs.a)
            return a < rhs.a;
        else if (b != rhs.b)
            return b < rhs.b;
        else
            return c < rhs.c;
    }
    
    const bool operator==(const P &rhs) const {
        return a == rhs.a && b == rhs.b && c == rhs.c;
    }
};

int n, k, tot, ans[100005], f[100005], sum[200005];
P p[100005];
map<P, int> ma;

void add(int id, int d) {
    while (id <= k) {
        sum[id] += d;
        id += lowbit(id);
    }
}

int query(int id) {
    int res = 0;
    
    while (id) {
        res += sum[id];
        id -= lowbit(id);
    }
    
    return res;
}

void mergeSort(int l, int r) {
    if (l == r)
        return;
    
    mergeSort(l, mid), mergeSort(mid + 1, r);
    
    int lptr = l, rptr = mid + 1, t = 0;
    P *tmp = new P[r - l + 2];
    
    while (lptr <= mid && rptr <= r)
        if (p[lptr].b > p[rptr].b) {
            tmp[++t] = p[rptr];
            ans[p[rptr].id] += query(p[rptr].c);
            ++rptr;
        } else {
            tmp[++t] = p[lptr];
            add(p[lptr].c, p[lptr].cnt);
            ++lptr;
        }
    
    while (lptr <= mid) {
        tmp[++t] = p[lptr];
        add(p[lptr].c, p[lptr].cnt);
        ++lptr;
    }
    
    while (rptr <= r) {
        tmp[++t] = p[rptr];
        ans[p[rptr].id] += query(p[rptr].c);
        ++rptr;
    }
    
    for (int i = l; i <= mid; ++i)
        add(p[i].c, -p[i].cnt);
    
    for (int i = l; i <= r; ++i)
        p[i] = tmp[i - l + 1];
    
    delete tmp;
}

int main() {
    scanf("%d %d", &n, &k);
    
    for (int i = 1; i <= n; ++i) {
        ++tot;
        scanf("%d %d %d", &p[tot].a, &p[tot].b, &p[tot].c);
        
        if (ma[p[tot]]) {
            ++p[ma[p[tot]]].cnt;
            --tot;
        } else {
            ma[p[tot]] = tot;
            p[tot].cnt = 1;
            p[tot].id = tot;
        }
    }
    
    sort(p + 1, p + tot + 1);
    mergeSort(1, tot);
    
    for (int i = 1; i <= tot; ++i)
        f[ans[p[i].id] + p[i].cnt - 1] += p[i].cnt;
    
    for (int i = 0; i < n; ++i)
        printf("%d\n", f[i]);
    
    return 0;
}
posted @   wf715  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示