1. 程式人生 > 實用技巧 >[學習筆記]多維偏序

[學習筆記]多維偏序

多維偏序

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高)

就是多維偏序了。