CDQ分治

如果现在有一些操作,有些操作会产生贡献,同时里面的情况会依次发生更改,要求我们去维护发生更改后的总贡献。

这个问题会使得我们初感很棘手,主要原因在于这是一个动态的问题,当其中一个操作发生变化后会对很多的操作产生影响,导致寻常的数据结构难以维护。

而现在引入的CDQ分治可以将一个动态问题分成几个静态问题,把操作的更改产生的贡献变得可以离线处理。

经过上述的叙述,我相信一定会使得对CDQ分治本就不多的理解雪上加霜,实际上,整个CDQ分治的过程我看来都是很抽象的,所以,下面将会引入一个例题,我们可以跟着例题进行探索。

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

题意:

给定几组数,每组包含了三个数\(a,b,c\),要求对于每一组数\(x\),要求找到一共有多少组数\(y\)满足\(a_x<=a_y,b_x<=b_y,c_x<=c_y\)。数据规模到达了\(5e5\)组数。

分析:

现在我们发现有三组关系,接下来我们可以考虑如何将三组关系一一解决。

首先,明显的我们可以直接以\(a\)为关键字从小到大排序,那么此时后面的数相对于前面的数一定满足第一个关系。

然后,我们可以开始进行最关键的一环:分治。我们设定一个子问题\(solve(l,r)\)表示将区间\(l,r\)之内的情况处理。显然,我们类比归并排序,将\((l,r)\)划分为\((l,mid)\)\((mid+1,r)\),先处理\(solve(l,mid)\)\(solve(mid+1,r)\),随后再处理区间\((l,mid)\)对区间\((mid+1,r)\)的影响。

我们可以接下来像归并排序一样,先将左右区间分别以\(b\)为第一关键字排序,然后我们就会发现如果我们在右区间从左到右递推,我们会发现按照单调性,我们可以解决掉第二个关系,左对右区间的影响也可以叠加。最后我们在套上一个树状数组就可以解决掉第三个关系。

CDQ分治的大致过程即是如此。下面将每个部分分别附上代码实现。

实现:

第一个关系的处理:排序

bool cmp1(node x,node y) {
	if(x.a!=y.a) return x.a<y.a;
	if(x.b!=y.b) return x.b<y.b;
	return x.c<y.c;
}

sort(A+1,A+n+1,cmp1); // main()内

CDQ分治:

bool cmp2(node x,node y) {
	if(x.b!=y.b) return x.b<y.b;
	return x.c<y.c;
}//按$b$为第一关键字排序
int T[maxn];
void modify(int x,int val) {for(;x<=k;x+=x&-x) T[x]+=val;}
int query(int x) {int ans=0; for(;x;x-=x&-x) ans+=T[x]; return ans;}
// 树状数组实现
void cdq(int l,int r) {
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid); cdq(mid+1,r);//划分子问题
	sort(A+l,A+mid+1,cmp2); sort(A+mid+1,A+r+1,cmp2);
	int j=l;
	for(int i=mid+1;i<=r;i++) {
		while(A[i].b>=A[j].b && j<=mid) {modify(A[j].c,A[j].cnt); j++;}//单调性递推
		A[i].ans+=query(A[i].c);//计算贡献
	}	
	for(int i=l;i<j;i++) modify(A[i].c,-A[i].cnt);//消除影响
}

AC代码:

#include<algorithm>
#include<stdio.h>
#include<queue>
#define maxn 200005
using namespace std;
int n,k,n1;
struct node {
	int a,b,c,cnt,ans;
} A[maxn];
bool cmp1(node x,node y) {
	if(x.a!=y.a) return x.a<y.a;
	if(x.b!=y.b) return x.b<y.b;
	return x.c<y.c;
}
void unique() {
	for(int i=1;i<=n;i++) {
		if(A[i].a!=A[i-1].a || A[i].b!=A[i-1].b || A[i].c!=A[i-1].c) A[++n1]=A[i];
		A[n1].cnt++;
	}
	n=n1;
}
bool cmp2(node x,node y) {
	if(x.b!=y.b) return x.b<y.b;
	return x.c<y.c;
}
int T[maxn];
void modify(int x,int val) {for(;x<=k;x+=x&-x) T[x]+=val;}
int query(int x) {int ans=0; for(;x;x-=x&-x) ans+=T[x]; return ans;}
void cdq(int l,int r) {
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid); cdq(mid+1,r);
	sort(A+l,A+mid+1,cmp2); sort(A+mid+1,A+r+1,cmp2);
	int j=l;
	for(int i=mid+1;i<=r;i++) {
		while(A[i].b>=A[j].b && j<=mid) {modify(A[j].c,A[j].cnt); j++;}
		A[i].ans+=query(A[i].c);
	}	
	for(int i=l;i<j;i++) modify(A[i].c,-A[i].cnt);
}
int ans[maxn];
int main() {
	freopen("P3810.in","r",stdin);
	freopen("P3810.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) {
		scanf("%d%d%d",&A[i].a,&A[i].b,&A[i].c);
	}
	sort(A+1,A+n+1,cmp1); unique();
	cdq(1,n);
	for(int i=1;i<=n;i++) {
		ans[A[i].ans+A[i].cnt-1]+=A[i].cnt;
	}
	for(int i=0;i<n;i++) {
		printf("%d\n",ans[i]);
	}
	return 0;
}

用途

CDQ分治是一种基于分治的思想,有时候对于一些在线问题我们会很难直接进行处理,这时我们就可以使用CDQ分治。

我们可以给操作加上一个数值,表示操作进行的时间刻,这使得我们可以判断操作进行的先后了,方便将修改问题转换为偏序问题,更有利于我们进行离线处理。

例题:P3157 [CQOI2011] 动态逆序对

对于本题的删除操作,我们可以考虑给排列中的每个数构造一组数:(位置,大小,修改时间)。

这样一来,我们会发现对于一个数,将其删去产生的影响为:位置在它前面,大小比它大,修改时间比它后的数个数,以及位置在它后面,大小比它小,修改时间比它后的数个数。

我们可以分成两次三维偏序操作,对于每次操作通过CDQ分治维护,最后将每个数产生的影响一次叠加即可。

#include<algorithm>
#include<stdio.h>
#include<queue>
#define maxn 100005
#define ll long long
using namespace std;
int n,m;
struct node {
	int pos,num,tim,ans;
} A[maxn];
int p[maxn],c[maxn];
bool cmp1(node x,node y) {return x.tim>y.tim;}
bool cmp2(node x,node y) {return x.pos>y.pos;}
int T[maxn];
void modify(int x,int val) {for(;x<=n;x+=x&-x) T[x]+=val;}
int query(int x) {int ans=0; for(;x;x-=x&-x) ans+=T[x]; return ans;}
void cdq1(int l,int r) {
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq1(l,mid),cdq1(mid+1,r);
	sort(A+l,A+mid+1,cmp1),sort(A+mid+1,A+r+1,cmp1);
	int j=l;
	for(int i=mid+1;i<=r;i++) {
		while(A[j].tim>=A[i].tim && j<=mid) {modify(A[j].num,1),j++;}
		A[i].ans+=query(n)-query(A[i].num);
	}
	for(int i=l;i<j;i++) modify(A[i].num,-1);
}
void cdq2(int l,int r) {
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq2(l,mid);
	cdq2(mid+1,r);
	sort(A+l,A+mid+1,cmp1),sort(A+mid+1,A+r+1,cmp1);
	int j=l;
	for(int i=mid+1;i<=r;i++) {
		while(A[j].tim>=A[i].tim && j<=mid) modify(A[j].num,1),j++;
		A[i].ans+=query(A[i].num);
	}
	for(int i=l;i<j;i++) modify(A[i].num,-1);
}
ll sum;
void cal() {
	for(int i=1;i<=n;i++) {
		sum+=query(n)-query(A[i].num);
		modify(A[i].num,1);
	}
	for(int i=1;i<=n;i++) modify(A[i].num,-1);
}
int main() {
//	freopen("P3157.in","r",stdin);
//	freopen("P3157.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d",&A[i].num); 
		A[i].pos=i,A[i].tim=n+1;c[A[i].num]=i;
	}
	for(int i=1;i<=m;i++) {
		scanf("%d",&p[i]);
		A[c[p[i]]].tim=i;
	}
	cal();
	cdq1(1,n);
	sort(A+1,A+n+1,cmp2);
	cdq2(1,n);
	sort(A+1,A+n+1,cmp1);
	for(int i=n;i>0;i--) {
		if(A[i].tim>n) continue;
		printf("%lld\n",sum);
		sum-=A[i].ans;
	}
	return 0;
}

THE END.

posted @ 2023-08-18 21:20  Ian8877  阅读(16)  评论(0编辑  收藏  举报