【数据结构】CDQ分治初步

目录

原理
代码

原理

模板题传送门:https://www.acwing.com/problem/content/2817/

cdq分治用来解决什么样的问题呢?一般来说可以:

  1. 统计具有三维属性 (a,b,c) 的、满足一定的比较关系 data 有多少对。
  2. 优化一些数据结构,算法。

流程:

下面说一下cdq分治解决上面模板题的流程。

排序解决第一维情况
首先我们将整个 data 集排序。(以 a 为第一关键字,以 b 为第二关键字,以 c 为第三关键字)
这样做便可以处理掉第一维的情况。

接下来,我们采取分治的思想来统计满足题意的对数。

对于目前需要处理的区间 [l,r]
mid=l+r2 ,左区间 [l,mid] ,右区间 [mid+1,r]

分情况讨论:

  • 假如满足条件的点对 (p,q) 都在左区间或者右区间,那么我们递归处理下去就好。
  • 否则,p 必然在左区间,q 必然在右区间,我们对这样的点对进行统计。

故重点在于第二种情况:
此时第一维情况(apaq)必然是满足的。

采取双指针解决第二维情况
假设现在的左右两个区间是按照 b 为关键字排好序了,我们记左区间的指针为 j ,右区间的指针为 i

因为我们的递归是自下而上的(基于归并排序的思想),所以我们可以在处理当前区间时进行以 b 为关键字的排序,这样就可以保证处理到每一个区间时都是以 b 为关键字的排序的了。

对于每一个 i ,我们让 j 右移,直到找到第一个 j ,满足 bj>bi ,那么对于 x[l,j1] 均有 bibx

采用树状数组解决第三维情况
最后,只要解决第三维情况,就可以统计出每一个 datai 相应的贡献(对数)了:对于元素 datax 其中 x[l,j1] ,我们将这样的 data 的属性 c 值放入树状数组中,只需查询一下 query(ci) 就可以找到满足 cicx个数了,注意到与此同时,前面两维情况也同时被满足,所以这样的个数就是所求的贡献。

细节:
我们记三维数据同时相等的 data同一类,发现分治的时候同一类之间并不好处理,所以我们将同一类进行合并,用 cnt 来记录每一类的个数,那么对不同类之间进行分治就可以了。

代码

#pragma GCC optimize("O3")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=1e5+5, M=2e5+5;
int n,m;
struct data{
	int a,b,c,cnt,res;
	
	bool operator<(const data &o)const{
		if(a!=o.a) return a<o.a;
		if(b!=o.b) return b<o.b;
		return c<o.c;
	}
	
	bool operator==(const data &o)const{
		return a==o.a && b==o.b && c==o.c;
	}
}e[N], tmp[N];
int tot;
int tr[M];

int lowbit(int x){return x&-x;}

void add(int p,int k){
	for(;p<M;p+=lowbit(p)) tr[p]+=k;
}

int query(int p){
	int res=0;
	for(;p;p-=lowbit(p)) res+=tr[p];
	return res;
}

void cdq(int l,int r){
	if(l>=r) return;
	int mid=l+r>>1;
	cdq(l,mid), cdq(mid+1,r);
	// 这里定义左区间对应的指针为 j, 右区间对应的指针为 i.
	for(int j=l, i=mid+1, k=l;k<=r;k++)
		if(i>r || j<=mid && e[j].b<=e[i].b) add(e[j].c,e[j].cnt), tmp[k]=e[j++]; // 如果说右区间的指针已经走到边界,或者左区间的b值比较小。
		else e[i].res+=query(e[i].c), tmp[k]=e[i++];
	for(int j=l;j<=mid;j++) add(e[j].c,-e[j].cnt); // 恢复桶
	for(int k=l;k<=r;k++) e[k]=tmp[k];  // 完成排序,复制
}

int ans[M];

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int a,b,c; cin>>a>>b>>c;
		e[i]={a,b,c,1};
	}
	
	sort(e+1,e+1+n);
	
	for(int i=1;i<=n;i++)
		if(e[i]==e[tot]) e[tot].cnt++;
		else e[++tot]=e[i];
	
	cdq(1,tot);
	
	for(int i=1;i<=tot;i++) ans[e[i].res+e[i].cnt-1]+=e[i].cnt;
	for(int i=0;i<n;i++) cout<<ans[i]<<'\n';
	
    return 0;
}
posted @   HinanawiTenshi  阅读(219)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示