三维偏序-陌上花开题解

D.陌上花开

题意:

\(n\) 个元素,第 \(i\) 个元素有 \(a_i,b_i,c_i\) 三个属性,设 \(f(i)\) 表示满足 \(a_j \leq a_i\)\(b_j \leq b_i\)\(c_j \leq c_i\)\(j \ne i\)\(j\) 的数量。

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

输入格式

第一行两个整数 \(n,k\),表示元素数量和最大属性值。

接下来 \(n\) 行,每行三个整数 \(a_i ,b_i,c_i\),分别表示三个属性值。

输出格式

\(n\) 行,第 \(d + 1\) 行表示 \(f(i) = d\)\(i\) 的数量。

样例

10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1
3
1
3
0
1
0
1
0
0
1

\(1 \leq n \leq 10^5\)\(1 \leq a_i, b_i, c_i \le k \leq 2 \times 10^5\)

题解思想及设计

  • 这是一个三维偏序问题。
  • 首先考虑二维偏序问题,两个关键字\((x,y)\),如果先按 \(x\) 从低到高排序,那就相当于降了一维,接下来解决一维偏序问题。
  • 对于一维偏序问题,一个数组\(a_1,a_2,...,a_n\),对于每一个\(i\),统计有多少个\(a_j\le a_i\)\(j\)的数量。若\(a_1<a_2<...<a_n\),则可以使用树状数组维护每个元素出现的次数\(c[N]\),从\(a_1\)\(a_n\)遍历,每遍历一个元素,更新对应的出现次数,然后询问答案即为前缀和\(c[1]+...+c[a_i]\),即为树状数组的一次查询。
  • 回到二维偏序,先以\(x\)作为第一关键字排序,\(y\)作为第二关键字排序。排序完后,处理一维偏序即可。
  • 对于三维偏序\((x,y,z)\),先以\(x\)作为第一关键字排序,\(y\)作为第二关键字排序,\(z\)作为第三关键字排序。若此时化为二维偏序,对\(y\)排序,会破坏原有\(x\)顺序,因此采用CDQ分治的思想解决问题。
  • 先以\(x\)作为第一关键字排序,\(y\)作为第二关键字排序,\(z\)作为第三关键字排序。然后将元素集分为两部分,\([1,mid],[mid+1,n]\),其中对于每一个部分而言,将问题划分成了两个同样的子问题,可以通过递归解决。对于跨部分的答案,在递归解决完两个子部分后合并时解决。
  • 假设区间\([l,mid],[mid+1,r]\)已经通过递归解决,合并时,左边部分的\(x\)值一定小于右边,因此只有左边部分的元素对右边部分的元素答案有贡献。在两个部分中分别对\(y\)进行排序,此时因为左边部分的\(x\)值一定小于右边,所以不会破坏\(x\)值的左右部分相对顺序。
  • 这个时候按照处理归并排序的合并思想类似(处理一维偏序),用一个数组\(c[N]\)维护左边部分元素的出现次数,使用双指针\(pl,pr\)\(pr\)从头到尾遍历,对于遍历到的每一个元素\(R_{pr}\),若\(pl\)指向的元素\(L_{pl}.y \le R_{pr}.y\)才有可能对当前\(pr\)指向的元素\(R\)的答案有贡献,更新\(c[L_{pl}]\)\(pl\)指针接着向后遍历,直到\(L>R\)。遍历完左指针后更新\(R\)的答案\(ans[R] = c[1]+c[2]+...+c[R]\)
  • 细节问题:可能要先对原输入数据进行去重和离散化处理;每一次合并之后要还原树状数组

伪代码

AC代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
//#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);
#define endl '\n' 
const int N = 2e5+10;
inline int lowbit(int x) {
    return x & -x;
}

struct BIT {
    int d[N],n=N-1;

    void add(int pos,int k) {
        for(int i=pos;i<=n;i+=lowbit(i)) {
            d[i]+=k;
        }
    }

    int query(int pos) {
        int ans=0;
        for(int i=pos;i>0;i-=lowbit(i)) {
            ans+=d[i];
        }
        return ans;
    }

} bit;

struct node {
    int a,b,c,cnt;
    int ans;
    node() {
        a=b=c=ans=cnt=0;
    }
};

int n,k;
node nodes[N];
node aa[N];


bool cmpb(const node& p,const node& q) {
    return p.b < q.b;
}

void solve(int l,int r) {
    if(l==r) return;
    int mid = (l+r)>>1;
    solve(l,mid);
    solve(mid+1,r);

    sort(nodes+l,nodes+mid+1,cmpb);
    sort(nodes+mid+1,nodes+r+1,cmpb);

    int i=l,j=mid+1;
    for(;j<=r;j++) {
        while(i<=mid && nodes[i].b <= nodes[j].b) {
            bit.add(nodes[i].c,nodes[i].cnt);
            i++;
        }
        nodes[j].ans+=bit.query(nodes[j].c);
    }

    for(int k=l;k<i;k++) {  
        bit.add(nodes[k].c, -nodes[k].cnt);
    }   

}


signed main()
{
    IOS
    cin>>n>>k;
    for(int i=1;i<=n;i++) {
        cin>>aa[i].a>>aa[i].b>>aa[i].c;
    }


    sort(aa+1, aa+n+1, [](const node &x, const node &y){
        if(x.a==y.a) {
            if(x.b==y.b) return x.c<y.c;
            else return x.b<y.b;
        } 
        return x.a<y.a;
    });

    // 合并重复三元组
    int index=0;int w=0;
    for(int i=1;i<=n;i++) {
        w++;
        if(aa[i].a!=aa[i+1].a || aa[i].b!=aa[i+1].b || aa[i].c!=aa[i+1].c) {
            nodes[++index].a=aa[i].a;
            nodes[index].b=aa[i].b;
            nodes[index].c=aa[i].c;
            nodes[index].cnt=w;
            w=0;
        }
    }
    int m=index;

    solve(1,m);

    int ans[N]={0};
    for(int i=1;i<=m;i++) {
        int f = nodes[i].ans + nodes[i].cnt - 1;
        ans[f] += nodes[i].cnt;
    }
    for(int i=0;i<=n-1;i++) {
        cout<<ans[i]<<endl;
    }

    return 0;
}

posted @ 2025-10-25 17:37  NightRainLone  阅读(23)  评论(0)    收藏  举报