cdq分治处理二维数点问题板子

本文介绍CDQ分治结合二维数点算法的基本原理及应用实例,包括结构体定义、比较函数、关键函数实现等内容,并通过具体例题进行讲解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

update at 2022/10/28,cdq+二维数点板子 

1.首先是结构体,一个node就是一个数点,b用来存数点,s是临时数组,cdq的时候会用到

//ty=1表示实际点,ty=2表示询问的虚点,v表示权值(实际点权值是1,虚点权值是询问权值)
struct node{int ty,x,y,v,id;};
node b[N*5], s[N*5];

2.然后是cmp函数,一般是按照{x,ty}二元组从小到大排序

bool cmp(const node&a,const node&b){
	if(a.x==b.x) return a.ty<b.ty; //如果x相同,ty小的放前面(因为x相等时实点能对询问贡献)
	else return a.x<b.x; //否则就按x排序,x小的放前面
}

 3.然后是add_point和add_query,要注意point的ty=1,query的ty=2,而具体函数怎么写要看题目要求,这个不是固定的。

void addP(int x,int y,int v){ //x,y是坐标,v是点的权值(本题权值都是1)
	b[++cnt] = {1,x,y,v,0};
}
void addQ(int x1,int y1,int x2,int y2,int id){ //id表示第id个询问
	b[++cnt] = {2,x2,y2,1,id};
	b[++cnt] = {2,x1-1,y2,-1,id};
	b[++cnt] = {2,x2,y1-1,-1,id};
	b[++cnt] = {2,x1-1,y1-1,1,id};
}

4.最后是cdq函数,大部分内容固定,统计答案部分要改

void cdq(int l,int r){
	if(l==r) return;
	int mid=l+r>>1;
	cdq(l,mid); cdq(mid+1,r);
	int pos = mid+1, sum=0, copy=l-1;
	FOR(i,l,mid){
		while(pos<=r && b[pos].y < b[i].y){
			s[++copy] = b[pos]; //归并排序正常操作(固定)
			//这一步,条件固定,但是后面的内容可能改
			if(b[pos].ty==2) ans[b[pos].id] += b[pos].v*sum;
			pos++;
		}
		if(b[i].ty==1) sum+=b[i].v; //累计左边贡献(固定)
		s[++copy] = b[i]; //归并排序正常操作(固定)
	}
	while(pos<=r){ //再把右半部分剩下的统计答案并且放入s数组
		s[++copy] = b[pos]; //(固定)
		//统计答案,条件固定,后面的可能改
		if(b[pos].ty==2) ans[b[pos].id] += b[pos].v*sum;
		pos++;
	}
	memcpy(b+l,s+l,sizeof(node)*(r-l+1)); //数组复制
}

 


例题:[SHOI2007]园丁的烦恼 - 洛谷 

1.cdq分治的好处:不需要离散化 

2.二维数点可以封装成两个函数,add_point和add_query。其中addP是二维上的单点修改,addQ是二维上一个矩形区域的询问。

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
const int N = 6e5+10;
int n,m; //n个点,m个修改/询问
struct node{int ty,x,y,v,id;}; //ty=1表示实际点,ty=2表示询问的虚点,v表示权值(实际点权值是1,虚点权值是询问权值)
node a[N*5], s[N*5];
int cnt, ans[N];

bool cmp(const node&a,const node&b){
	if(a.x==b.x) return a.ty<b.ty; //如果x相同,ty小的放前面(因为x相等时实点能对询问贡献)
	else return a.x<b.x; //否则就按x排序,x小的放前面
}
void addP(int x,int y,int v){ //x,y是坐标,v是点的权值(本题权值都是1)
	a[++cnt] = {1,x,y,v,0};
}
void addQ(int x1,int y1,int x2,int y2,int id){ //id表示第id个询问
	a[++cnt] = {2,x2,y2,1,id};
	a[++cnt] = {2,x1-1,y2,-1,id};
	a[++cnt] = {2,x2,y1-1,-1,id};
	a[++cnt] = {2,x1-1,y1-1,1,id};
}
void cdq(int l,int r){
	if(l==r) return; //处理完了,结束
	int mid=l+r>>1;
	cdq(l,mid); cdq(mid+1,r);
	int pos = mid+1, sum=0, copy=l-1;
	FOR(i,l,mid){
		while(pos<=r && a[pos].y < a[i].y){
			s[++copy] = a[pos]; //归并排序正常操作
			if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //[l,i-1]部分能对a[pos]贡献,从i开始不能了,所以统计答案
			pos++;
		}
		if(a[i].ty==1) sum+=a[i].v; //累计贡献
		s[++copy] = a[i]; //归并排序正常操作
	}
	while(pos<=r){ //再把右半部分剩下的统计答案并且放入s数组
		s[++copy] = a[pos];
		if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //统计答案
		pos++;
	}
	memcpy(a+l,s+l,sizeof(node)*(r-l+1)); //数组复制
}
void solve(){
    cin>>n>>m;
	FOR(i,1,n){
		int x,y; cin>>x>>y;
		addP(x,y,1);
	}
	FOR(i,1,m){
		int x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; //左下角和右上角
		addQ(x1,y1,x2,y2,i);
	}
	sort(a+1,a+cnt+1,cmp); //二维数点按x排序
	cdq(1,cnt); //cdq分治处理
	FOR(i,1,m) cout<<ans[i]<<'\n';
}
signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T=1; //cin>>T;
    while(T--) solve();
}

当然,有很多其他问题也可以转化成二维数点问题,从而用cdq+二维数点解决,比如:

【1】单点修改,区间询问问题,比如这个:

【模板】树状数组 1 - 洛谷

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define int long long
const int N = 5e5+10;
int n,m; //n个点,m个修改/询问
struct node{int ty,x,y,v,id;}; //ty=1表示实际点,ty=2表示询问的虚点,v表示权值(实际点权值是1,虚点权值是询问权值)
node a[N*5], s[N*5];
int cnt=0, ans[N];
int tot=0;

bool cmp(const node&a,const node&b){
	if(a.x==b.x) return a.ty<b.ty; //如果x相同,ty小的放前面(因为x相等时实点能对询问贡献)
	else return a.x<b.x; //否则就按x排序,x小的放前面
}
void addP(int x,int y,int v){ //x,y是坐标,v是点的权值(本题权值都是1)
	a[++cnt] = {1,x,y,v,0};
}
void addQ(int x1,int y1,int x2,int y2,int id){ //id表示第id个询问
	a[++cnt] = {2,x2,y2,1,id};
	a[++cnt] = {2,x1-1,y2,-1,id};
	a[++cnt] = {2,x2,y1-1,-1,id};
	a[++cnt] = {2,x1-1,y1-1,1,id};
}
void cdq(int l,int r){
	if(l==r) return; //处理完了,结束
	int mid=l+r>>1;
	cdq(l,mid); cdq(mid+1,r);
	int pos = mid+1, sum=0, copy=l-1;
	FOR(i,l,mid){
		while(pos<=r && a[pos].y < a[i].y){
			s[++copy] = a[pos]; //归并排序正常操作
			if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //[l,i-1]部分能对a[pos]贡献,从i开始不能了,所以统计答案
			// if(ans[a[pos].id]>0) cout<<a[pos].id<<' '<<ans[a[pos].id]<<endl;
			pos++;
		}
		if(a[i].ty==1) sum+=a[i].v; //累计贡献
		s[++copy] = a[i]; //归并排序正常操作
	}
	while(pos<=r){ //再把右半部分剩下的统计答案并且放入s数组
		s[++copy] = a[pos];
		if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //统计答案
		pos++;
	}
	memcpy(a+l,s+l,sizeof(node)*(r-l+1)); //数组复制
}
void print(int x){
	cout<<a[x].ty<<' '<<a[x].x<<' '<<a[x].y<<' '<<a[x].v<<endl;
}
void solve(){
	cin>>n>>m;
	FOR(i,1,n){
		int x; cin>>x;
		addP(i,i,x); //一维是时间,一维是位置,权值是x
	}
	FOR(i,n+1,n+m){
		int ty,x,y; cin>>ty>>x>>y;
		if(ty==1) addP(i,x,y); //修改
		else if(ty==2) addQ(1,x,i,y,++tot); //询问
	}
	sort(a+1,a+cnt+1,cmp);
	cdq(1,cnt);
	FOR(i,1,tot) cout<<ans[i]<<'\n';
}
signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T=1; //cin>>T;
    while(T--) solve();
}

【2】逆序对问题(待更...)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值