[学习笔记]2023多维偏序之cdq分治

多维偏序

https://www.cnblogs.com/Miracevin/p/9426812.html

https://blog.csdn.net/MorphLing_/article/details/107219077?utm_medium=distribute.pc_relevant.none-task-blog-searchFromBaidu-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-searchFromBaidu-3.control

 

https://blog.csdn.net/u012972031/article/details/91049130

 

 

 


一般情况下,我们比较一个数大小,就是ai>aj即可,

而在上升子序列中,当i>j并且ai>aj的时候,才可以认为i这位的数大于j这位的数。

这就是一个二维偏序。

 

类似的,有n个数,每个数m个属性,一个数比另一个数大,当且仅当这个数的所有属性都大于另一个数。

这就是一个m维偏序。

 

对于三维偏序,可以用cdq分治、排序、树状数组处理。

luogu P3810
Description:
有 n 个元素,第 i 个元素有 ai​ 、bi​ 、ci​ 三个属性,设 f(i) 表示满足 aj​≤ai​ 且bj​≤bi​ 且cj​≤ci​ 的 j 的数量。

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

Solution:
三维偏序的模板题。

我们先按照a从小到大排序,

然后cdq分治。

先递归到两边,

回溯到这一层之后,把左儿子的所有的数按照b排序,右儿子的所有数也按照b排序。

这样,左儿子的数之间虽然a不一定递增,但是因为开始按照a排序,所以左边所有的数的a一定都小于右边的数。

b排好序之后,

两个指针j,i分别从1~mid,mid+1~r 即左右儿子区间的起止点开始走,,

对于右边的一个数i,当j的数的b值不大于i的b值时,不断向后走j,并且把这些数的c值放进一个权值树状数组里,

当j的b值大于i之后,当前所有左儿子里面,b小于i这个数的数的c属性都放进树状数组里了。

只有放进去的这些数才可能来更新f值。

在i向后走之前,f[a[i].ans]+=query(a[i].z),满足第三个条件的数也找到了。

就把所有当前这个层里面,符合条件的数都找出来了。

最后,把树状数组加上的 1都消去。

 

由于cdq分治,会把i之前的所有的数都分成logn个区间,更新完f[i]了。

大家可以手动画图,或者模拟一下。

 

复杂度:nlogn^2

 

那么,这个算法是怎么样实现三维偏序的处理呢?

1.对于a,开始直接排序,并且,每次是先递归左右儿子,再处理这一层,

所以保证一个数pi前面的所有的数,不论之后怎么换位置,都不会到i的后面。

这就利用位置保证了所有可能更新f[i]的数对于a都是合法的。

2.对于b,我们每次回溯的时候,按照b排了一个序,

对于左子区间对右子区间的影响,通过指针,把b小于等于i的数的所有数的c放进了树状数组里。

这样,i前面的logn个区间,会把所有b小于i的b的数都考虑一遍的。也合法。

3.对于c,直接通过树状数组前缀和,一步就求出来了当前合法的所有数了。

相当于一个筛,留下a合法的,留下b合法的,最后能被c留下的,就是所有合法的了。

 

注意因为是小于等于号,所以我们先把所有的完全相同的数合并成一个数,统计的时候,一个数也大于等于自己。再加上就好了。

就是细节问题。

 

样例细解

输入数据
4 19
1 12 5.........0
2 4 3..........0
3 9 6........1
4 15 19.......3


输出
2
1
0
1

对于(1,12,5),(2,4,3),(3,9,6),(4,15,19)
当递归处理(1,2)时,先处理(1,1),再处理(2,2)
再对(1,1)排序,(2,2)排序
左边为(1,12,5),右边为(2,4,3)
因为12>4所以根本加入不了
当递归处理(3,4)时
因为9<15,所以在6这个位置加入1
于是对于(4,15,19)来说,可以加入(3,9,6)的贡献

当递归处理(1,4)时
(2,4,3),(1,12,5)|(3,9,6),(4,15,19)
对于整个区间[,4]来说第1维是上升的
即对于(3,9,6),它左边的元素的第1维都是<=3的,现在先要保证
第二维左边是升序的,所以要先排序
排好后
对于(3,9,6)来说(2,4,3)可以有贡献,但(1,12,5)则是没有的
因为12>9
对于(4,15,19)来说(1,12,5)是有贡献的
因而(4,15,19)是有3个小于它的

总得来说就是
先用排序来保证第一维
在处理区间[L,R]的时候,对两边[L,Mid],[Mid+1,R]进行排序
以输入时的第2关键字为主关键字,第3关键字为次关键字
于是就可以采用指针法,对于[Mid+1,R]中的某个对象j
将左边的那些,第2关键字小于等于它的对象i
在i的第3关键字所代表的位置上加入贡献值

 

#include<bits/stdc++.h>
using namespace std;
const int N=100000+10;
int n,m;
int f[N];
struct node{
    int x,y,z;
    int ans;
    int w;
}a[N],b[N];
int pp;
bool cmpx(node p,node q){
    if(q.x==p.x){
        if(p.y==q.y) return p.z<q.z;
        return p.y<q.y;
    }
    return p.x<q.x;
}
bool cmpy(node p,node q){
    if(q.y==p.y){
        return p.z<q.z;
    }
    return p.y<q.y;
}
struct ta{
    int g[2*N];
    void add(int x,int k){
        for(;x<=pp;x+=x&(-x)) g[x]+=k;
    }
    int ask(int x){
        int ret=0;
        for(;x;x-=x&(-x)) ret+=g[x];return ret;
    }
}t;

void cdq(int l,int r){
    //cout<<l<<" and "<<r<<endl;
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq(l,mid);cdq(mid+1,r);
    sort(a+l,a+mid+1,cmpy);
    sort(a+mid+1,a+r+1,cmpy);
    //cout<<" after sort "<<l<<" || "<<r<<endl;
    int i=mid+1,j=l;
    for(;i<=r;i++){
        while(a[j].y<=a[i].y&&j<=mid){
            t.add(a[j].z,a[j].w),j++;
        }
        a[i].ans+=t.ask(a[i].z);
    }
    for(i=l;i<j;i++){
        t.add(a[i].z,-a[i].w);
    }
}
int ans[N];
int main()
{    
    int m;
    scanf("%d%d",&m,&pp);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].z);
    }
    sort(b+1,b+m+1,cmpx);
    int c=0;
    for(int i=1;i<=m;i++){
        c++;
        if(b[i].x!=b[i+1].x||b[i].y!=b[i+1].y||b[i].z!=b[i+1].z){
            a[++n]=b[i],a[n].w=c;c=0;
        }
    }
    cdq(1,n);
    for(int i=1;i<=n;i++){
        ans[a[i].ans+a[i].w-1]+=a[i].w;
    }
    for(int i=0;i<m;i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}

三维偏序

  

要是维数再多了呢??

hihocoder 1236 Scores
Decripiton:
给出N个人的5个科目分数,给出q个询问~~,每次给你5个科目的具体分数。求一共有多少个人对应的5个科目都小于等于你的科目分数~~

强制在线。

n,q<=50000

Solution:
这就是5维偏序了。

显然cdq分治不容易解决了。难以巧妙处理5维。

就考虑比较暴力的思路:

把成绩5种分成5组,每一组从小到大按成绩排序。

每次二分出id,id的成绩恰好位于成绩查询边界。

这样可以知道该成绩是有几个人不满足。

但是,由于有5个,所以必须知道都是谁。。。。

用一个bitset<50001>s[5][50000]表示,第i维,前j个人满足不合格的情况下,都是谁(s[i][j][k]=1表示,i维,编号是k的人,成绩比倒数第j名可能还差。)

然后预处理出bitset(每次j加一,就把j+1的人或进去就行了),查询的时候,二分id,之后取出这5个bitset,&一下就知道最后剩谁了,统计1的个数。

 

但是bitset还是太大了,不是MLE,就是TLE。

所以,考虑分块!??!

bitset<50001>s[5][250]表示,第i维,前j块不合格,都是谁

预处理比较容易,一个块一个块内暴力处理,最后从前到后相邻的块一个前缀或就可以了

查询的时候,

二分出来一个id,在块k里,

就找到k,把k-1块的不合格人都找出来,之后剩下的暴力加进去就可以啦

 

复杂度:O(5 * q * sqrtn)(不算预处理)

因为这个是n<=50000,所以分块卡不掉。

这种数据范围,多维偏序都可以类似扩展开来。

 

本质上还是一个筛。不过用了bitset和分块优化。

 

总结:

感觉多维偏序在生活中还是比较常见的,

比方说,你期末考试之后,想看看完虐多少个人?(每一科都比TA高)

就是多维偏序了。

posted @ 2020-12-30 21:53  我微笑不代表我快乐  阅读(148)  评论(0编辑  收藏  举报