三维偏序(陌上花开)

Posted tian-luo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三维偏序(陌上花开)相关的知识,希望对你有一定的参考价值。

三维偏序(陌上花开)

题目背景

这是一道模板题

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

题目描述

有 n 个元素,第 i 个元素有 ai?bi?ci? 三个属性,设 f(i) 表示满足 aj?ai? 且 bj?bi? 且 cj?ci? 的 j 的数量。

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

输入输出格式

输入格式:

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

之后 n 行,每行三个整数 ai?bi?ci,分别表示三个属性值。

输出格式:

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

输入输出样例

输入样例: 
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
输出样例: 
3
1
3
0
1
0
1
0
0
1

说明

1n100000,1k200000

题目链接:https://www.luogu.org/problemnew/show/P3810


CDQ分治,首先按对原序列进行三维排序,然后运用分治的思想,分治过程向下的时候保证a有序,分治过程回溯的时候保证b有序,某一分治中间态,我们只考虑mid左边对mid右边的影响(同在左边或者同在右边通过分治解决),因为mid左边的a一定小于等于mid右边的a,然后我们按照归并排序的方法使得b也有序,这样对于mid右边的数来说,当前中间态有且仅有左边的且b小于等于该数的数才能对它有贡献,于是可以维护一个树状数组,在对b进行归并排序的过程中,统计左边的c出现的次数,并计算左边的数对右边的数的贡献。

小细节:若两个数的a,b,c值都相同,那么如果直接进行CDQ分治会导致相同数的贡献不能全部计算的情况,因为CDQ分治保证了每次计算左边的数对右边的数的贡献,但是相同的数无论左右都对彼此有贡献。所以要先去重,然后用一个权值代表该数出现了多少次。

技术图片
#include<bits/stdc++.h>
using namespace std;

const int N = 2e5+50;
struct ss

    int a,b,c,value,ans;

    bool operator < (const ss &s)const
    
        if(a!=s.a)return a<s.a;
        if(b!=s.b)return b<s.b;
        return c<s.c;
    

    bool operator == (const ss &s)const
    
        return (a==s.a&&b==s.b&&c==s.c);
    

;

ss arr[N],ls[N];
pair<int,int> ls2[N];

int c[N];
void update(int pos,int v)

    for(int i=pos; i<N; i+=i&(-i))c[i]+=v;

int Sum(int pos)

    int ans=0;
    for(int i=pos; i>0; i-=i&(-i))ans+=c[i];
    return ans;


void print(int l,int r)

    printf("%d %d:\n",l,r);
    for(int i=l; i<=r; i++)printf("%d %d %d %d\n",arr[i].a,arr[i].b,arr[i].c,arr[i].ans);
    printf("\n");


void cdq(int l,int r) //分治 

    if(l==r)return;
    int mid=(l+r)/2;
    cdq(l,mid);    //先解决子问题 
    cdq(mid+1,r);

    int sum_ls2=0;

    int i=l,j=mid+1,k=l;

//print(l,r);

    while(i<=mid&&j<=r)
    
        if(arr[i].b<=arr[j].b)//如果左边的数的b比较小,就更新树状数组,以便后边统计答案 
        
            update(arr[i].c,arr[i].value);
            ls2[sum_ls2++]= pair<int,int>(arr[i].c,arr[i].value);
            ls[k++]=arr[i++];
        
        else//右边的数的b值比较小,那么此时左边的剩余的数的b值一定都大于该数的b值,则此时对该数有贡献的- 
           //-数一定都是前面加到树状数组里的数 
            arr[j].ans+=Sum(arr[j].c);
            ls[k++]=arr[j++];
        
    

    while(i<=mid)
    
        ls[k++]=arr[i++];
    

    while(j<=r)
    
        arr[j].ans+=Sum(arr[j].c);
        ls[k++]=arr[j++];
    

    for(int i=0; i<sum_ls2; i++)update(ls2[i].first,-ls2[i].second);//不能memset清空树状数组 
    for(int i=l; i<=r; i++)arr[i]=ls[i];

    //print(l,r);



int ans[N]= 0;
int main()

    int n,k;
    scanf("%d %d",&n,&k);
    for(int i=1; i<=n; i++)scanf("%d %d %d",&arr[i].a,&arr[i].b,&arr[i].c),arr[i].ans=0;

    sort(arr+1,arr+1+n);
    int pos=1;
    arr[1].value=1;

    for(int i=2; i<=n; i++)  //去重 
    
        if(arr[i]==arr[i-1])
        
            arr[pos].value++;
        
        else
        
            arr[++pos]=arr[i];
            arr[pos].value=1;
        
    

//    for(int i=1;i<=pos;i++)printf("%d %d %d %d\n",arr[i].a,arr[i].b,arr[i].c,arr[i].value);


    cdq(1,pos);
    for(int i=1; i<=pos; i++)
    
        arr[i].ans+=arr[i].value-1;  //加上相同数的贡献 
        ans[arr[i].ans]+=arr[i].value;
    

    for(int i=0; i<n; i++)printf("%d\n",ans[i]);
    return 0;

View Code

 

以上是关于三维偏序(陌上花开)的主要内容,如果未能解决你的问题,请参考以下文章

陌上花开(三维偏序)

三维偏序(陌上花开)

P3810 -三维偏序(陌上花开)cdq-分治

题解-luogu P3810三维偏序(陌上花开)

Luogu 3810 & BZOJ 3262 陌上花开/三维偏序 | CDQ分治

(三维偏序)陌上花开