[學習筆記]多維偏序
多維偏序
https://www.cnblogs.com/Miracevin/p/9426812.html
一般情況下,我們比較一個數大小,就是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留下的,就是所有合法的了。
注意因為是小於等於號,所以我們先把所有的完全相同的數合併成一個數,統計的時候,一個數也大於等於自己。再加上就好了。
就是細節問題。
#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高)
就是多維偏序了。