• CDQ分治学习笔记(三维偏序题解)


    首先肯定是要膜拜CDQ大佬的。

    题目背景

    这是一道模板题

    可以使用bitset,CDQ分治,K-DTree等方式解决。

    题目描述

    有 nn 个元素,第 ii 个元素有 a_iaib_ibic_ici 三个属性,设 f(i)f(i) 表示满足 a_j leq a_iajai 且 b_j leq b_ibjbi 且 c_j leq c_icjci 的 jj 的数量。

    对于 d in [0, n)d[0,n),求 f(i) = df(i)=d 的数量

    输入输出格式

    输入格式:

    第一行两个整数 nn、kk,分别表示元素数量和最大属性值。

    之后 nn 行,每行三个整数 a_iaib_ibic_ici,分别表示三个属性值。

    输出格式:

    输出 nn 行,第 d + 1d+1 行表示 f(i) = df(i)=d 的 ii 的数量。

    输入输出样例

    输入样例#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

    怎么看三维偏序呢?

    其实就是一个三维的逆序对,没有这么高大上。

    回想二维逆序对(普通逆序对)的求解方法:

    1、树状数组

    利用树状数组的前缀和查询性质,把原数组离散化成相对大小,然后从后往前查询,插入

    2、归并排序

    归并排序(这和CDQ分治很像,非常像,甚至可以说:归并排序就是cdq分治求二维偏序)

    于是,回到正题,三维逆序对,哦不,是三维偏序。

    第一维把它排序(在二维逆序对里,第一维是排好序的)

    第二维把它塞到归并排序里,去求满足二维的个数,如果满足,塞到树状数组里

    第三维用树状数组维护前缀和,就像求二维逆序对那样直接找出来个数。因为经过了前两层的筛选,这一层统计的个数就是最终的答案。

    对于CDQ来说,其实最难得部分就是怎么合并两个区间。

    说起来非常简单吧。。。但是作为一直规避归并排序的我来说,还是有小小的问题的。。

    塞代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+10;
    struct node
    {
        int nth,a,b,c;
    }a[maxn];
    int n,k;
    int f[maxn],same[maxn],t[maxn<<1],ans[maxn];
    inline int lowbit(int x)//树状数组
    {
        return x & - x ;
    }
    void add(int x,int y)
    {
        for(;x<=k;x+=lowbit(x))
        {
            t[x]+=y;
        }
    }
    int ask(int x)
    {
        int res=0;
        for(;x;x-=lowbit(x))
        {
            res+=t[x];
        }
        return res;
    }
    bool cmp1(node a,node b)//对第一维进行排序
    {
        if(a.a!=b.a)return a.a<b.a;//第一关键字自然是第一个元素
        if(a.b!=b.b)return a.b<b.b;//第二关键字尽量保证(题目要求大于等于)
        else    return a.c<b.c;
    }
    bool cmp2(node a,node b)
    {
        if(a.b!=b.b)return a.b<b.b;//归并时第二维进行排序
        if(a.c!=b.c)return a.c<b.c;
        else    return a.a<b.a;
    }
    void cdq(int l,int r)
    {
        if(l==r)
        return;
        int mid=l+r>>1;
        cdq(l,mid);
        cdq(mid+1,r);
        sort(a+l,a+1+r,cmp2);//直接排序,下面计算答案贡献
        for(int i=l;i<=r;i++)
        {
            if(a[i].a<=mid)//如果元素小于mid就说明符合二维的偏序
            {
                add(a[i].c,1);//塞到树状数组里面
            }
            else
            {
                ans[a[i].nth]+=ask(a[i].c);//要不然就统计答案
            }
        }
        for(int i=l;i<=r;i++)
        {
            if(a[i].a<=mid)
            add(a[i].c,-1);这里还要更新回去,给下一个分治用(注意,不能用memset,太大了,而且上面的if一定要加,可以省去不少个log的复杂度)
        }
    }
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c);
            a[i].nth=i;//第一维
        }
        sort(a+1,a+n+1,cmp1);
        for(int i=1;i<=n;)
        {
            int j=i+1;
            while(j<=n&&a[i].a==a[j].a&&a[i].b==a[j].b&&a[i].c==a[j].c)
            j++;
            while(i<j)
            same[a[i++].nth]=a[j-1].nth;//去重
        }
        for(int i=1;i<=n;i++)
        {
            a[i].a=i;
        }
        cdq(1,n);
        for(int i=1;i<=n;i++)
        f[ans[same[a[i].nth]]]++;
        for(int i=0;i<n;i++)
        printf("%d
    ",f[i]);
        return 0;
    }
  • 相关阅读:
    vmware 网络连接
    【剑指offer】设置在最小数目的阵列
    动画语音输入和文字输入开关
    Qt 如何处理拖放应用程序参数时,中国
    C#
    dojo的TabContainer添加ContentPane假设closable,怎么不闭幕后予以销毁ContentPane
    剑指XX(游戏10)
    PHP的MySQL扩张:MySQL数据库概述
    所谓策略,我站在旁边看今天 神刻的样子O2O
    两个新事物
  • 原文地址:https://www.cnblogs.com/ajmddzp/p/11055995.html
Copyright © 2020-2023  润新知