[学习笔记]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高)
就是多维偏序了。