首页 > 其他分享 >CDQ分治

CDQ分治

时间:2023-08-18 21:36:02浏览次数:37  
标签:node sort int 分治 mid 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.

标签:node,sort,int,分治,mid,CDQ
From: https://www.cnblogs.com/1n87/p/17641645.html

相关文章

  • 分治算法——241. 为运算表达式设计优先级
    分治思路:对于一个算式来说,总是可以根据运算符分为左右两部分算式,接着分别计算结果并合并;每一个结果都是一个数组,包含这个算式的所有可能结果,计算时将左右两部分排列组合;递归的终点是字符串是纯数字(即分到一个算式中只剩下一个数字),直接返回。 比如示例中的2*3-4*5,有下面的......
  • 与点对有关的CDQ分治(菜鸟笔记)
    参考文章   首先要说明的是CDQ是一种思想,并且扩展范围很广。   这里主要说的是与点对有关的CDQ。参考文章说了与CDQ主要解决的三大类问题。第一类就是解决和点对有关的问题。主要是给定一个长度为n的序列,然后找出其中满足题意的点对\((i,j)\)。   CDQ的......
  • 分治算法C++
    1、光荣的梦想题目描述】Prince对他在这片陆上维护的秩序感到满意,于是决定启程离开艾泽拉斯。在他动身之前,Prince决定赋予King_Bette最强大的能量以守护世界、保卫这里的平衡与和谐。在那个时代,平衡是个梦想。因为有很多奇异的物种拥有各种不稳定的能量,平衡瞬间即被打破。KB决定求......
  • 浅谈根号分治
    浅谈根号分治一、问题引入  给定一个长度为\(n\)的序列,进行\(m\)次询问。每次询问给出两个数字\(x,y\)。对于每次询问,输出所有下标模除\(x\)等于\(y\)的元素的总和。  对于这个问题,我们发现他要维护的是一段离散的元素的和,而我们平时学的数据结构,如线段树等都只能维护一段......
  • 【学习笔记】线段树分治
    定义线段树分治是一种解决一类有插入、删除和整体查询操作的问题的方法。它是一种离线做法,通过在线段树上记录操作的时间区间来处理修改对询问的影响。每个操作被看作一个时间区间的修改,并在线段树上进行标记。然后通过深度优先搜索(DFS)依次执行这些操作,直到根节点来回答查询,并在......
  • 根号分治-2023牛客7 E-Star Wars
      也就是说对于大点和小点我们采用不同的方式维护对于大点来说我们只需要记录它的周围点的总和不需要知道具体的谁链接了它 对于小点我们需要维护它的所有信息他自己链接了哪些点 需要再开一个vector表示自己链接的大点这样大对大或者小对大的时候维护的信息也......
  • 【W的AC企划 - 第六期】树上分治
    往期浏览树上分治点分治讲解每次选取树的重心进行递归分治,中阶算法,大部分模板不需要理解,但是每一题都需要对维护函数进行修改。复杂度\(\mathcalO(N\logN)\)。个人封装由于需要进行一定程度的修改,不符合结构体封装的原则,故没有使用结构体。introot=0,MaxTree=1e1......
  • 点分治
    点分治模板1注意vis数组的作用,相当于把树切割注意变量sum的作用,第一次写的时候没用sum,全部用了n,导致没有正确找到树的重心,然后tle了#include<bits/stdc++.h>usingll=longlong;constintN=1E4+5,M=105,O=1E7+10;constintINF=......
  • [Ynoi2016] 这是我自己的发明(根号分治+分块/莫队)
    题目传送门soltion简单题换根显然可以拆成\(O(1)\)个区间,这里先不管。直接做法是莫队,把双子树拆成\(dfs\)序上的双前缀,可以直接莫队,但是常数比较大。另一种做法是根分,对颜色出现次数分治,大于的求出\(dfs\)序的前缀和即可,小于的因为一共只有\(O(n\sqrtn)\)个点对,所以......
  • C/C++ 数据结构五大核心算法之分治法
    分治法——见名思义,即分而治之,从而得到我们想要的最终结果。分治法的思想是将一个规模为N的问题分解为k个较小的子问题,这些子问题遵循的处理方式就是互相独立且与原问题相同。两部分组成:分(divide):递归解决较小的问题治(conquer):然后从子问题的解构建原问题的解三个步骤:1、......