三维偏序-陌上花开题解
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;
}

浙公网安备 33010602011771号