首页 > 其他分享 >cdq分治

cdq分治

时间:2024-07-23 23:09:06浏览次数:19  
标签:偏序 归并 int 分治 mid cdq 排序

简介

前置芝士:归并排序。

\(cdq\) 分治是个离线算法,可以解决三维偏序或者优化 \(dp\)。

题目

直接上个题目:陌上花开

维护三维偏序有个口诀:一维排序,二维归并,三位数据结构。

考虑第一维直接排序解决掉,然后还剩两维。

我们考虑第二维用归并排序解决掉。然后假设当前区间 \([l,r]\),区间中点 \(mid\)。那么我们通过递归可以解决 \([l,mid],[mid+1,r]\) 内部的贡献,现在要考虑解决两个点在两个区间中的贡献。

我们在递归时已经按照第二维排好序了。但是有个问题,第一维的限制呢?这样不是把第一维的限制忽略掉了吗?

再想一想我们求的东西,两个端点分别在两个区间内,而这时两个区间内的第一维的相对大小仍然是满足要求的,因为这时他们还没有完成第二维的排序。

然后如果我们发现这时第二维满足了要求,我们就把第三维扔进树状数组或者权值线段树内(推荐树状数组)。否则(重点),此时的 \([l,i-1]\) 是一个极大的 \(\forall u\in[l,i-1],u\) 满足前两维的偏序关系的区间。这时我们就要统计贡献了。

然后就是,我们把没跑完的部分跑完,但是这里的循环顺序非常重要,不能更改,具体原因写在代码注释里了。

最后记得堆原数组去个重,不然会出问题。

代码:

#include<bits/stdc++.h>
#define int long long
#define N 200005
using namespace std;
int n,m,k,c[N],siz[N],f[N],res[N];
struct node{
	int x,y,z,id;
	bool operator<(const node &t)const{
		if(x!=t.x)return x<t.x;
		if(y!=t.y)return y<t.y;
		return z<t.z;
	}
	bool operator!=(const node &t)const{
		return x!=t.x||y!=t.y||z!=t.z;
	}
}a[N],b[N];
int lowbit(int x){//树状数组三件套1
	return x&-x;
}
void add(int x,int v){//树状数组三件套2
	while(x<=k){
		c[x]+=v;
		x+=lowbit(x);
	}
}
int qry(int x){//树状数组三件套3
	int res=0;
	while(x){
		res+=c[x];
		x-=lowbit(x);
	}
	return res;//求有多少数比x小
}
void cdq(int l,int r){
	if(l>=r)return;
	int mid=l+r>>1;
	cdq(l,mid);cdq(mid+1,r);//归并排序
	int i=l,j=mid+1,now=0;
	while(i<=mid&&j<=r){
		if(a[i].y<=a[j].y){//如果满足,证明还不是极大区间
			add(a[i].z,siz[a[i].id]);//那么就把这个数存进去,注意有多个一起存
			b[now++]=a[i++];//归并排序
		}
		else{
			res[a[j].id]+=qry(a[j].z);//这时区间达到极大,可以统计
			b[now++]=a[j++];
		}
	}
	while(j<=r){//1,如果放在2后面会导致统计错误
		res[a[j].id]+=qry(a[j].z);
		b[now++]=a[j++];
	}
	for(int x=l;x<i;x++){//2,如果放到3后面会导致清空错误(多清空)
		add(a[x].z,-siz[a[x].id]);
	}
	while(i<=mid){//3,不能放到2前面,原因见2
		b[now++]=a[i++];
	}//上面这3个循环的顺序不能换
	for(i=l,j=0;i<=r;i++,j++){
		a[i]=b[j];//归并排序
	}
}
signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i].x>>a[i].y>>a[i].z;
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++){
		if(a[i]!=a[i-1])b[++m]=a[i];//这里是去重
		siz[m]++;//记录相同的元素个数
	}
	for(int i=1;i<=m;i++){
		a[i]={b[i].x,b[i].y,b[i].z,i};//记录去重后的数组
	}
	cdq(1,m);
	for(int i=1;i<=m;i++){
		int id=a[i].id;
		//数量为去重后统计的答案再加上其他相同的元素的数量
		f[res[id]+siz[id]-1]+=siz[id];//这里减1是因为去掉自己
	}
	/*
	这里笔者写的时候有个疑惑,为什么把id改成i是对的,原因如下:
	首先,这两个写法从逻辑上来说不等价,但是结果上来说等价
	因为原来的id是按照第一维排序的,上面的i是按照第二维排序的,所以两者本质不同
	但是,由于id是不重复的,所以id恰好遍历1-m
	所以从结果上来说两者等价
	*/
	for(int i=0;i<n;i++){
		cout<<f[i]<<'\n';
	}
	return 0;
}

标签:偏序,归并,int,分治,mid,cdq,排序
From: https://www.cnblogs.com/zxh923aoao/p/18319795

相关文章

  • 离线分治算法:cdq 分治
    \(\texttt{0x00}\):前置芝士归并排序;树状数组;重载运算符(这个大概都会吧)。\(\texttt{0x01}\):介绍cdq分治是一种离线分治算法,可用来处理以下几种问题:解决和点对有关的问题。1D动态规划的优化与转移。通过CDQ分治,将一些动态问题转化为静态问题。它们的本质都是通过一......
  • 分治法 -----归并排序、逆序对、最大子数组和
    一分治法概念分治(divide-and-conquer),顾名思义分而治之。分治的核心是将原问题分解为同类型的规模更小的子问题,子问题往往可以求解或者求解比较简单,通过整合子问题的解得到原来问题的解。分治的过程可以用如下图来表示:由上述图示可发现整个分治过程是一颗树,而且子问题的处......
  • 线段树分治
    线段树分治线段树分治可以解决这样的问题:对于一些操作,每个操作只在\(l\)~\(r\)的的时间内有效。对于一些操作,询问某个时间点所有操作的贡献。经典例题算法思路对于这道题,因为二分图的充要条件是不存在奇环,所以可以使用扩展域并查集解决。我们考虑离线优化:对于每......
  • CDQ分治
    该分治算法由CDQ提出,主要用于解决三维偏序问题。下面的内容就三维偏序例题来讲。题目给你一个序列,每个元素有\(a,b,c\)三个属性,问满足\(a_i>a_j,b_i>b_j,c_i>c_j\)的数对\(i,j\)的数量。分析将原序列按照\(a\)值排序,将其变为下标。CDQ分治的主要步骤是对于一个需要......
  • CDQ 分治学习笔记
    CDQ分治的流程大致是将对于区间\([l,r]\)中点\(x,y\)的计数分为两类处理:\(x,y\)均位于\([l,mid]\)或\([mid+1,r]\)中,这样的点对贡献可以递归解决。\(x,y\)分别位于\([l,mid]\)和\([mid+1,r]\)中,这样的点对通过一些操作来统计贡献。显然这两类贡献之和即为......
  • 点分治学习笔记
    分治就是将一个问题划分成多个子问题来求解。点分治就是在树上进行分治,一般是来统计路径信息的。对于以\(u\)为根的子树内,所有的路径可以划分为两类,一类跨越了子树,经过了\(u\),另一类没有跨越,只经过子树中的点,而子树内的点又可以再分类,这就是点分治。为了保证时间复杂度,每次......
  • 点分治
    点分治及其应用算法:点分治,树的重心。思想先说一下点分治的基本思想:选择树上一个点作为分治中心,为了保证复杂度,选择的点有一些特殊的要求。接下来,把原问题分解成几个相同的子问题,进行递归解决。一般地,我们假设当前根节点为\(rt\),所以我们要统计的路径必然满足以下二者之一:......
  • 分治
    快速排序每次找一个基准值,比基准值小的放左边,比基准值大的放右边,在分别对左右排序标准代码:voidwork(intl,intr){ if(l>=r)return; intmid=(l+r)/2; mid=a[mid]; intx=l,y=r; while(x<=y) { while(a[x]<mid)x++; while(a[y]>mid)y--; ......
  • 线段树分治学习笔记
    线段树分治是一种通过线段树维护时间轴,实现一些可撤销的信息维护问题的手段。线段树分治是离线算法。具体地,对于若干个修改与询问,按照时间戳像区间修改一样挂在线段树的节点上,然后遍历整棵线段树,将节点上的操作计入贡献,对于一个时间戳为\(t\)的询问,线段树上区间\([t,t]\)即......
  • cdq分治
    cdq分治其大致形式为递归左右半边考虑左半边对右半边的影响二维偏序(归并排序):计算数对\((i,j)\)满足\(a_i<a_j\)并且\(b_i<b_j\)的数对数量先以\(a_i\)排序,这样条件被转换为\(i<j\)且\(b_i<b_j\)考虑cdq分治先递归左右半边对左右两边各开一个指针,若\(b_i<b_......