LGP3810_1 [LG TPLT] 陌上花开,三维偏序 学习笔记

LGP3810_1 [LG TPLT] 陌上花开,三维偏序 学习笔记

Luogu Link

题意简述

\(n\) 个元素,第 \(i\) 个元素有 \(a_i,b_i,c_i\) 三个属性,设 \(f(i)\) 表示满足 \(a_j\le a_i,b_j\le b_i,c_j\le c_i\)\(j\) 的数量。对于 \(d\in [0,n)\),求 \(f(i)=d\) 的数量。

做法解析

我们先来回忆一下二维偏序是怎么做的:把序列先按照 \(a_i\) 从小到大排序,对于每一个元素 \(i\),查找它前面有多少元素满足 \(b_j<b_i\),然后再把 \(i\) 加进去。

那么对于三维偏序,我们不妨也把序列先按照 \(a_i\) 从小到大排序。然后呢?这个时候CDQ分治就可以派上用场——

CDQ分治的思想就是:对于一个区间 \([l,r]\),先分别计算 \([l,mid]\)\([mid,r]\) 的贡献,再计算跨过 \(mid\) 的贡献。我们来考虑怎么计算跨过区间中点的贡献。因为我们预先把序列先按照 \(a_i\) 从小到大排序,所以一定有 \(a_{[l,mid]}<a_{[mid+1,r]}\)

我们把左右两边各自按照 \(b_i\) 排序,然后开始双指针,这个过程类似于从上文中静态二维偏序转化为动态一维偏序的过程:\([l,mid]\) 上一个指针 \(p\)\([mid+1,r]\) 上一个指针 \(q\),我们现在要查的就是 \(\sum_q c_q>c_[l,p]\) 的贡献。

pEQ0JQe.md.png

注意往下递归那一步要放在算跨中点答案前面,否则递归前就要重新按 \(a_i\) 排一遍序。

可以发现我们递归了 \(\log N\) 层,每层都要对 \(b_i\) 排序,所以时间复杂度 \(O(N\log^2 N)\)

代码实现

\(a_i,b_i,c_i\) 完全相同的元素合并了。

#include <bits/stdc++.h>
using namespace std;
namespace obasic{
    template <typename _T>
    void readi(_T &x){
        _T k=1;x=0;char ch=getchar();
        for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
        for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
        x*=k;return;
    }
    template <typename _T>
    void writi(_T x){
        if(x<0)putchar('-'),x=-x;
        if(x>9)writi(x/10);
        putchar(x%10+'0');
    }
};
using namespace obasic;
const int MaxN=1e5+5,MaxK=2e5+5;
int N,nln,K,X,Y,Z,ans[MaxK];
struct anob{
    int x,y,z,t,s;
    friend bool operator==(anob a,anob b){
        return a.x==b.x&&a.y==b.y&&a.z==b.z;
    }
}A[MaxN];
bool cmpx(anob a,anob b){return a.x!=b.x?a.x<b.x:(a.y!=b.y?a.y<b.y:a.z<b.z);}
bool cmpy(anob a,anob b){return a.y!=b.y?a.y<b.y:(a.x!=b.x?a.x<b.x:a.z<b.z);}
struct BinidTree{
    int n,t[MaxK];
    void init(int x){n=x,fill(t,t+n+1,0);}
    int lowbit(int x){return x&(-x);}
    void add(int p,int x){for(;p<=n;p+=lowbit(p))t[p]+=x;}
    int gts(int p){int res=0;for(;p;res+=t[p],p-=lowbit(p));return res;}
}BidTr;
void cdqdac(int l,int r){
    if(l==r)return;int mid=(l+r)>>1;
    cdqdac(l,mid),cdqdac(mid+1,r);
    sort(A+l,A+mid+1,cmpy),sort(A+mid+1,A+r+1,cmpy);
    int p=l,q=mid+1;for(;q<=r;q++){
        for(;p<=mid&&A[p].y<=A[q].y;p++){
            BidTr.add(A[p].z,A[p].t);
        }
        A[q].s+=BidTr.gts(A[q].z);
    }
    for(int i=l;i<p;i++)BidTr.add(A[i].z,-A[i].t);
}
int main(){
    readi(N),readi(K);
    for(int i=1;i<=N;i++){
        readi(X),readi(Y),readi(Z);
        A[i]={X,Y,Z,1,0};
    }
    sort(A+1,A+N+1,cmpx),nln=1;
    for(int i=2;i<=N;i++){
        if(A[i]==A[nln])A[nln].t++;
        else A[++nln]=A[i];
    }
    sort(A+1,A+nln+1,cmpx);
    BidTr.init(K),cdqdac(1,nln);
    for(int i=1;i<=nln;i++)ans[A[i].s+A[i].t-1]+=A[i].t;
    for(int i=0;i<N;i++)writi(ans[i]),puts("");
    return 0;
}

反思总结

具体到每道题目里面,根据是小于还是小于等于代码会有一定区别。
有一点要注意的:如果偏序关系可以取等,那么排序时三个关键字都要考虑!否则可能会出现诸如 \(i<j,a_i=a_j,b_i>b_j\) 这类情况(第一维相等而后面的维度没有正式排序,导致自己在左边,自己要收的贡献在右边)。
反之,如果不能取等,考虑当前关键字就行了!
当然,也要看清楚是小于号还是大于号。

posted @ 2025-02-20 15:40  矞龙OrinLoong  阅读(25)  评论(0)    收藏  举报