P3810 【模板】三维偏序(陌上花开)

原题链接

解法:CDQ分治

求解三维偏序问题的方法很多,我用的是 CDQ 分治,比较好写,但时间复杂度会劣一些。

CDQ 分治是离线算法,可以解决一些与点对有关的问题,也可以将动态问题转为静态。在此题中,我们有三个维度 a , b , c a,b,c a,b,c,运用 CDQ 解决的流程如下:

①:先按 a a a 从小到大排序,这样满足任意 j > i j>i j>i,有 a j > = a i a_j>=a_i aj>=ai

②:进入 CDQ 分治,设此时处理区间为 [ l , r ] [l,r] [l,r],令中点为 m i d mid mid,先分别递归处理左右两个子区间,然后再考虑跨区间的贡献。类似分治思想。

③:我们将 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 两段区间内按 b b b 排序,接下来设 i i i j j j 两个指针,找到所有 i < = m i d i<=mid i<=mid b i < = b j b_i<=b_j bi<=bj,这个时候 a a a b b b 两维都满足了,我们可以开一个值域树状数组,用以维护 c c c 的数量。最终就求出答案了。

时间复杂度: O ( n l o g 2 n ) O(n_{}log^2n) O(nlog2n)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int n,m,k,tr[N],ans[N];
struct node{
	int a,b,c,res,cnt;
	bool operator !=(node x){
		if(a!=x.a) return true;
		if(b!=x.b) return true;
		if(c!=x.c) return true;
		return false;
	}
}q[N],e[N];
bool cmpa(node a,node b){
	if(a.a!=b.a) return a.a<b.a;
	if(a.b!=b.b) return a.b<b.b;
	return a.c<b.c;
}
bool cmpb(node a,node b){
	if(a.b!=b.b) return a.b<b.b;
	return a.c<b.c;
}
int lowbit(int x){return x&-x;}
void add(int x,int v){for(int i=x;i<=2e5;i+=lowbit(i))tr[i]+=v;}
int query(int x){int res=0;for(int i=x;i;i-=lowbit(i))res+=tr[i];return res;}
void CDQ(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	sort(e+l,e+mid+1,cmpb),sort(e+mid+1,e+r+1,cmpb);
	int i=l,j=mid+1;
	while(j<=r){
		while(i<=mid&&e[i].b<=e[j].b) add(e[i].c,e[i].cnt),i++;
		e[j].res+=query(e[j].c),j++;
	}
	for(int k=l;k<i;k++) add(e[k].c,-e[k].cnt);//别忘了清空,防止对之后计算产生影响
}
int main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++) q[i].a=read(),q[i].b=read(),q[i].c=read();
	sort(q+1,q+1+n,cmpa);
	int t=0;
	for(int i=1;i<=n;i++){//a,b,c都相等的点视为一个,防止干扰。
		t++;
		if(q[i]!=q[i+1]){
			e[++m].a=q[i].a,e[m].b=q[i].b,e[m].c=q[i].c;
			e[m].cnt=t,t=0;
		}
	}
	CDQ(1,m);
	for(int i=1;i<=m;i++) ans[e[i].res+e[i].cnt-1]+=e[i].cnt;
	for(int i=0;i<n;i++) cout<<ans[i]<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值