HDU 5618 CDQ分治(三维偏序)

题意:

题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=5618
给出n个有序对(a,,b,c),对于每个(ai,bi,ci)找到满足aj <=ai,bj<=bi,cj<=ci的有序对有多少个。


思路:

典型的三维偏序问题,可以用CDQ分治来解决。
首先将有序对按照a的大小排序,这样对区间[L,R)进行分治的时候就可以默认左半部分[L,M)的a要小于等于右半部份[M,R)的a。按照CDQ分治的思想,先递归处理左右两个部分的子问题,然后处理左半部分对于右半部份的影响,就这题来说,就是对左右两部分按照b的大小排序,按照归并排序的写法先进入临时数组tmp的有序对b一定不大于之后进的。那么现在关键是处理c,因为有c存在不能直接计数,注意到所有数字的大小只有1e5,这时候考虑用树状数组求和来统计c的个数,就如同求逆序对的作法一样。对于左半部分的每一个有序对在来更新树状数组,右半部份的每个有序对来查询树状数组,从而计数。
这题还有几个地方需要注意:
1.定义Query大小关系的时候,要记得包括小于等于的情况。
2.排序的时候不能只按照a的大小排序,还要按照先b后c确定优先级,因为这样才能保证右边区间的有序对能找到所有比优先级他小的。
3.按照上面的做法,如果有m个完全一样的有序对(a,b,c),这m个有序对所求的结果受到位置的影响并不一样,因为排在后面的可以统计前面的,而前面的却没有统计后面的,所以一开始排序排在m个中最后的那个有序对才是这m个有序对共同的答案。
4.记得在每一次的分治都要清空树状数组。


代码:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;

struct node {
    int x, y, z, id;
} a[MAXN], b[MAXN];

bool cmp1(const node &aa, const node &bb) {
    if (aa.x != bb.x) return aa.x < bb.x;
    return (aa.y < bb.y) || (aa.y == bb.y && aa.z < bb.z);
}

bool cmp2(const node &aa, const node &bb) {
    return (aa.y < bb.y) || (aa.y == bb.y && aa.z < bb.z);
}

int C[MAXN], ans[MAXN];

void add(int x, int y) {
    while (x < MAXN) {
        C[x] += y;
        x += x & -x;
    }
}

int sum(int x) {
    int res = 0;
    while (x) {
        res += C[x];
        x -= x & -x;
    }
    return res;
}

void cle(int x) {
    while (x < MAXN) {
        C[x] = 0;
        x += x & -x;
    }
}

void solve(int l, int r) {
    if (l == r) return;
    int m = (l + r) >> 1;
    solve(l, m);
    for (int i = l; i <= r; i++) b[i] = a[i];
    sort (b + l, b + m + 1, cmp2);
    sort (b + m + 1, b + r + 1, cmp2);
    for (int j = m + 1, i = l; j <= r; j++) {
        while (i <= m && b[i].y <= b[j].y) {
            add(b[i].z, 1); ++i;
        }
        ans[b[j].id] += sum(b[j].z);
    }
    for (int i = l; i <= m; i++) cle(b[i].z);
    solve(m + 1, r);
}

int main() {
    //freopen("in.txt", "r", stdin);
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);
            a[i].id = i;
        }
        sort (a + 1, a + 1 + n, cmp1);
        memset(C, 0, sizeof(C));
        memset(ans, 0, sizeof(ans));
        solve(1, n);
        for (int i = n - 1; i >= 1; i--) {
            if (a[i].x == a[i + 1].x && a[i].y == a[i + 1].y && a[i].z == a[i + 1].z) {
                ans[a[i].id] = ans[a[i + 1].id];
            }
        }
        for (int i = 1; i <= n; i++)
            printf("%d\n", ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值