bzoj[3262]陌上花开==洛谷P3801 (CDQ分治+树状数组)

2 篇文章 0 订阅
1 篇文章 0 订阅

bzoj[3262]陌上花开==洛谷P3801 (CDQ分治+树状数组)

题意:三维偏序。
给你N个花,每个花有三个属性。一个花的三个属性都分别大于等于另一朵花的时候,等级+1,问从0到N-1级,每一级有多少朵花。

题解:这题目可以树套树,CDQ套CDQ,KD树等等。这里用CDQ分治+树状数组来解题目和解释。这里花的三个我用x,y,z来表示
总的来讲就是,第一维度就直接sort排序,第二维度用CDQ分治来维护,最后,第三维度用树状数组来维护。
这里是的树状数组是权值树状数组
A:第一维度的值我们用SORT来维护之后,即排完序之后,我们把第二维度的值给分成两个部分,[L,MID]和[MID+1,R],因为第一维度已经排过序列了,所以[L,MID]里面所有的x都比[MID+1,R]要大。
B:所以,我们把[L,MID]都先打一个标记F=-1。然后把数组按照y从小到大排序,之后遍历一遍,当发现这个F的标记是-1的时候,我们知道这个值的X一定在X小的那块,我们把这个花的Z值给加到树状数组中,然后遇到不是-1的东西的时候,我们在树状数组中查询一遍这个值的前缀和。
C:因为在打标记的时候,我们已经能确定哪些值的X会是小的(标记成-1),然后CDQ排序后,遍历的时候,我们也能确定这些Y值会是从小到大的,那么我们最后只需要向二维偏序那样去确定,比当前这个点的Z值还要小的,花的Z值有多少个。
D:最后类似归并排序那样递归深搜,就OK了。
E:注意CDQ分治并不能去确定重复一样的值,所以我们要离散化去重,而且在比较大小排序的时候,一定要写完整。因为这两个原因,笔者WA了一下午。我太菜了。。
F:若还是迷迷糊糊的,可以结合代码+注释阅读。

PS:这个CDQ分治主要是一种思想吧,计算左边的区间对于右区间的贡献。CDQ分治也是基于归并排序的一种变形和利用吧。
据说CDQ分治的发明人和莫队和主席树的发明人同一届,什么神仙啊。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx = 2e6 + 7;
int bit[mx * 4];
int ans[mx];
int ans2[mx];
int hasha[mx]={0};
struct node {
    int x, y, z, id;
    int cnt,f;
} a[mx], b[mx],d[mx];
int lowbit(int x) {
    return x & (-x);
}
void add(int i, int x) {
    while (i <= mx) {
        bit[i] += x;
        i += lowbit(i);
    }
}
int sum(int i) {
    int s = 0;
    while (i > 0) {
        s += bit[i];
        i -= lowbit(i);
    }
    return s;
}
bool cmpx(node s, node t) {   //一定要写详细,WA到自闭
    if (s.x == t.x) {
        if (s.y == t.y) {
            if (s.z == t.z)
                return s.id < t.id;
            else
                return s.z < t.z;
        } else
            return s.y < t.y;
    } else
        return s.x < t.x;
}
bool cmpy(node s, node t){  //一定要写详细,WA到自闭
    if (s.y == t.y) {
        if (s.z == t.z) {
            if (s.x == t.x)
                return s.id < t.id;
            else
                return s.x < t.x;
        } else
            return s.z < t.z;
    } else
        return s.y < t.y;
}
void cdq(int l, int r) {
    if (l >= r)
        return;
    int mid = (l + r) >> 1;
    int cnt = 0;
    for (int i = l; i <= r; i++) {
        cnt++;
        b[cnt] = d[i];
        if (i <= mid)
            b[cnt].f = -1;     //给前半部分小的加上标记,表示这些花的X都是比较小的
        else
            b[cnt].f = d[i].id;   
    }
    sort(b + 1, b + 1 + cnt, cmpy);   //sort y的值,这样就能保证y值从小到大遍历
    for (int i = 1; i <= cnt; i++) {
        if (b[i].f == -1)
            add(b[i].z, b[i].cnt);   //把x值小的那一半加入树状数组,这个cnt表示重复的个数
        else
            ans[b[i].id] += sum(b[i].z);    //如果是大的,则是查询前缀,就是比这个Z的X和Y都小的有几个,x靠标记已经确定,而Y是利用这个排序可以确定小的。
    }
    for (int i = 1; i <= cnt; i++)
        if (b[i].f == -1)
            add(b[i].z, -b[i].cnt);   //因为要递归深搜所以注意,把树状数组这次操作过的地方清零
    cdq(l, mid);         //递归左右,类似归并,能做到不重不漏
    cdq(mid + 1, r);
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
#ifdef ACM_LOCAL
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    memset(bit, 0, sizeof(bit));
    int n, s;
    cin >> n;
    cin >> s;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].x >> a[i].y >> a[i].z;
        ans2[i] = ans[i] = 0;
    }
    sort(a + 1, a + 1 + n, cmpx);  //因为sort过了,所以可以保证X一定会小于
    int num = 0;
    for (int i = 1; i <= n; i++) {  //手撕一个unique去重
        a[i].id = i;
        if (i != 1 && a[i].x == a[i - 1].x && a[i].y == a[i - 1].y && a[i].z == a[i - 1].z)
            d[num].cnt++;
        else {
            d[++num] = a[i];
            d[num].cnt = 1;   //记得标记相同的有多少个
        }
    }
    cdq(1, num);
    int res = 0;
    for (int i = 1; i <= num; i++)
        ans[d[i].id] += d[i].cnt - 1;
    for (int i = 1; i <= num; i++) {
        for (int j = 1; j <= d[i].cnt; j++)
            ans2[++res] = ans[d[i].id];
    }     //离散化去重回来,逆过程
    for (int i = 1; i <= res; i++)
        hasha[ans2[i]]++;//cout << ans[i] << endl;
    for (int i = 0; i <= n - 1; i++)
        cout << hasha[i] << endl;  //输出每个等级的花有多少
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值