cdq 分治处理二维三维偏序问题

学习上参考了这篇博客: pecco的笔记

cdq 的主要是想就是:

  1. 递归前一半
  2. 判断前一半对后一半的影响
  3. 递归后一半

但是需要注意,如果偏序关系带等号,对于完全相等的元素,需要捆绑起来一起处理,不然可能会把相等的元素划到左右两个区间去,从而把相等两元素的之间相互的贡献漏掉一半。

1.二维偏序

例: 处理逆序对

//nlog^2n
ll cdq(int A[], int n)
{
    if (n == 1)
        return 0;
    int mid = n / 2, i = 0, j = mid;
    ll ans = cdq(A, mid) + cdq(A + mid, n - mid);
    sort(A, A + mid, greater<int>()), sort(A + mid, A + n, greater<int>());
    for (; j < n; ++j)
    {
        while (i < mid && A[i] > A[j])
            i++;
        ans += i;
    }
    return ans;
}
//nlogn
ll cdq(int A[], int n)
{
    static int B[MAXN]; // 暂存归并排序的结果
    if (n == 1)
        return 0;
    int mid = n / 2, i = 0, j = mid, k = 0;
    ll ans = cdq(A, mid) + cdq(A + mid, n - mid);
    for (; j < n; ++j)
    {
        while (i < mid && A[i] > A[j])
            B[k++] = A[i++];
        ans += i, B[k++] = A[j];
    }
    while (i < mid)
        B[k++] = A[i++];
    memcpy(A, B, sizeof(int) * k);
    return ans;
}

注意到, 这个同样可以用树状数组处理.

signed main()
{
    int n; cin >> n;
    vector<int>a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

	//离散化 a 数组

    int ans = 0;
    for (int i = 0; i < n; ++i) {
        ans += i - ask(a[i]);
        upd(a[i], 1);
    }

    cout << ans << endl;
}
3.三维偏序

先在外部按第一维排好序。在函数体内,递归地解决左区间和右区间的子问题,然后对于一个在左区间,一个在右区间的点对,我们分别将左右区间按第二维排序,第二维用双指针, 第三维用树状数组解决问题。

具体地,对于右区间的每个点,我们遍历左区间,把第二维符合要求的点的第三维塞入树状数组,然后查询第三维也符合要求的点的数目(即上树状数组求逆序对方法)。

为了节约时间,清空树状数组不使用memset,而是原路删除。

eg: 当然也可以选择 cdq 套 cdq, 而不是 cdq 套树状数组

//https://www.luogu.com.cn/problem/P3810
bool cmpa(node x, node y) {
    if (x.a == y.a) {
        if (x.b == y.b)return x.c < y.c;
        return x.b < y.b;
    }
    return x.a < y.a;
}
bool cmpb(node x, node y) {
    if (x.b == y.b)return x.c < y.c;
    return x.b < y.b;
}

signed main()
{
    int n, k;
    cin >> n >> k;
    vector<int>num(n, 0);
    vector<node>a, b(n);
    for (int i = 0; i < n; ++i) {
        cin >> b[i].a >> b[i].b >> b[i].c;
        b[i].w = 1;
    }
    sort(b.begin(), b.end(), cmpa);

    int cnt = 0;//捆绑处理完全相同的元素
    for (int i = 0; i < n; ++i) {
        cnt++;
        if (b[i].a != b[i + 1].a || b[i].b != b[i + 1].b || b[i].c != b[i + 1].c) {
            a.emplace_back(b[i]);
            a.back().w = cnt;
            a.back().ans = 0;
            cnt = 0;
        }
    }
    n = a.size();

    auto cdq = [&](auto self, int l, int r) {
        if (l == r)return ;
        int mid = (l + r) >> 1;
        self(self, l, mid); self(self, mid + 1, r);
        sort(a.begin() + l, a.begin() + mid + 1, cmpb);
        sort(a.begin() + mid + 1, a.begin() + r + 1, cmpb);

        int p1 = l, p2 = mid + 1;
        for (; p2 <= r; ++p2) {
            while (p1 <= mid && a[p1].b <= a[p2].b) {
                upd(a[p1].c, a[p1].w);
                ++p1;
            }
            a[p2].ans += ask(a[p2].c);
        }

        for (--p1; p1 >= l; --p1) {//这里用原路返回清空 tr,节省时间
            upd(a[p1].c, -a[p1].w);
        }
    };

    cdq(cdq, 0, n - 1);

    for (int i = 0; i < n; ++i) {num[a[i].ans + a[i].w - 1] += a[i].w;}//统计答案为 i 的元素的个数 num[i]
    for (auto c : num)cout << c << endl;
}
  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值