【数据结构】CDQ分治初步
发布日期:2021-05-09 00:23:53 浏览次数:21 分类:原创文章

本文共 2370 字,大约阅读时间需要 7 分钟。

目录

原理
代码

原理

模板题传送门:

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

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

流程:

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

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

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

对于目前需要处理的区间 \([l,r]\)
\(mid=\lfloor \frac{l+r}{2} \rfloor\) ,左区间 \([l,mid]\) ,右区间 \([mid+1,r]\)

分情况讨论:

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

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

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

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

对于每一个 \(i\) ,我们让 \(j\) 右移,直到找到第一个 \(j\) ,满足 \(b_j>b_i\) ,那么对于 \(x\in[l,j-1]\) 均有 \(b_i\geq b_x\)

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

细节:
我们记三维数据同时相等的 \(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;}
上一篇:【DP】例解单调队列优化
下一篇:【数据结构】可持久化线段树初步

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2025年05月16日 02时03分24秒