[BZOJ 3262] [Luogu P3810] 【模板】三维偏序(陌上花开)

BZOJ传送门
洛谷传送门

题目描述

有n朵花,每朵花有三个属性:花形(s)、颜色(c)、气味(m),用三个整数表示。
现在要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量。
定义一朵花A比另一朵花B要美丽,当且仅 Sa>=Sb,Ca>=Cb,Ma>=Mb S a >= S b , C a >= C b , M a >= M b
显然,两朵花可能有同样的属性。需要统计出评出每个等级的花的数量。

输入输出格式

输入格式

第一行为 N,K N , K ( 1<=N<=100,000,1<=K<=200,000 1 <= N <= 100 , 000 , 1 <= K <= 200 , 000 ), 分别表示花的数量和最大属性值。
以下N行,每行三个整数 si,ci,mi s i , c i , m i ( 1<=si,ci,mi<=K 1 <= s i , c i , m i <= K ),表示第i朵花的属性

输出格式

包含N行,分别表示评级为 0...N1 0... N − 1 的每级花的数量。

输入输出样例

输入样例#1:
10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1
输出样例#1:
3
1
3
0
1
0
1
0
0
1

解题分析

CDQ板子题, 不过对于刚学习CDQ的萌新来说还是弄了很久…
我们先考虑:假如是二维偏序我们怎样处理?显然是排序一维, 另一维做 LIS L I S ,总复杂度 nlog(n) n l o g ( n )

现在多了一维, 显然无法做 LIS L I S 了。我们可以考虑二维树状数组, 但蒟蒻不会…

这时就可以搬出神奇的 CDQ C D Q 分治了。 首先, 我们需要知道使用CDQ分治的两个基本要求:
1.修改操作对询问的贡献独立,修改操作之间互不影响效果(即后面的修改不会对前面的点产生影响)。
2.题目允许使用离线算法。(因为CDQ分治就是利用离线降低编程复杂度和时间复杂度)

对于这道题来说, 第一维排序, 第二维CDQ, 第三维BIT即可。
我们可以将每个区间 [lef,rig] [ l e f , r i g ] 的贡献分为三部分:
1. [lef,mid] [ l e f , m i d ] 区间内的独立贡献, 递归即可。
2. [mid+1,rig] [ m i d + 1 , r i g ] 区间的独立贡献, 递归即可。
3. [lef,mid][mid+1,rig] [ l e f , m i d ] 对 [ m i d + 1 , r i g ] 的贡献:因为第一维已经排序, 右区间第一维的值一定大于第二维的值, 那么我们再将左右区间的第二维排序。 将每个点的第二维和第三维视为平面上的坐标,考虑按第二维从左至右处理左右区间每个点, 若在左区间则在树状数组按第三维上加上该点的权值, 若在右区间则在树状数组中查询小于等于该点第三维的权值进行转移。

如果还没有想明白, 可以看下下面这张图:

图中蓝色点表示左区间的点, 粉色点代表右区间的所有点, 可以看出来我们按第二维排序后对于每个粉色节点只需要查询第三维的一个前缀和即可。

其他细节详见代码。代码如下:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <cstring>
#include <cstdlib>
#define R register
#define W while
#define IN inline
#define gc getchar()
#define MX 200005
#define lowbit(i) (i & (-i))
template <class T>
IN void in (T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
namespace CDQ
{
    struct Dat
    {
        int a, b, c, cnt, sum;
    }cpy[MX], rd[MX];
    int tree[MX], ans[MX], dot, avai, lim;
    IN bool cmp1 (const Dat &x, const Dat &y)
    //第一次不仅需要将一位排序, 并且注意要将第二维第三维作为第二第三关键字排序, 
    //否则在分治时按第二维划分可能出现第一维相同而第二维较小的被分在右区间去的情况
    {
        if(x.a != y.a) return x.a < y.a;
        if(x.b != y.b) return x.b < y.b;
        return x.c < y.c;
    }
    IN bool cmp2 (const Dat &x, const Dat &y) {return x.b < y.b;}
    IN bool operator != (const Dat &x, const Dat &y)
    {return x.a != y.a || x.b != y.b || x.c != y.c;}
    IN void add(R int pos, const int &del)//树状数组操作
    {W (pos <= lim) tree[pos] += del, pos += lowbit(pos);}
    IN int query(R int pos)
    {R int ret = 0; W (pos) ret += tree[pos], pos -= lowbit(pos); return ret;}
    void cdq(const int &lef, const int &rig)
    {
        if(lef == rig) return;
        int mid = lef + rig >> 1;
        cdq(lef, mid); cdq(mid + 1, rig);
        //应该先将左右区间独立处理, 否则若先sort会破坏区间内第一维排序的结果
        std::sort(cpy + lef, cpy + mid + 1, cmp2);
        std::sort(cpy + mid + 1, cpy + rig + 1, cmp2);
        int lb = lef, rb = mid + 1;
        W (rb <= rig)
        {
            W (lb <= mid && cpy[lb].b <= cpy[rb].b)
            add(cpy[lb].c, cpy[lb].cnt), ++lb;
            cpy[rb].sum += query(cpy[rb].c), ++rb;
        }
        for (R int i = lef; i < lb; ++i) add(cpy[i].c, -cpy[i].cnt);//将树状数组清空
    }
}
using namespace CDQ;
int main(void)
{
    int a, b, c;
    in(dot); in(lim);
    for (R int i = 1; i <= dot; ++i)
    {
        in(a), in(b), in(c);
        rd[i] = {a, b, c, 0, 0};
    }
    std::sort(rd + 1, rd + 1 + dot, cmp1);
    for (R int i = 1; i <= dot; ++i)
    {
        if(rd[i] != rd[i - 1])
        cpy[++avai] = rd[i];
        ++cpy[avai].cnt;
    }
    cdq(1, avai);
    for (R int i = 1; i <= avai; ++i)
    ans[cpy[i].sum + cpy[i].cnt - 1] += cpy[i].cnt;
    for (R int i = 0; i < dot; ++i)
    printf("%d\n", ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值