浅谈CDQ分治

Ⅰ、预备知识

整体二分???

Ⅱ、抛出问题

我们先来看一道洛谷的模板题

题目背景

这是一道模板题
可以使用bitset(不会),CDQ分治,K-DTree(不会)等方式解决。

题目描述

\(n\)个元素,第\(i\)个元素有\(a_i\)\(b_i\)\(c_i\)三个属性,设\(f(i)\)表示满足\(a_j\leq a_i\)\(b_j\leq b_i\)\(c_j\leq c_i\)\(j\)的数量。
对于\(d\in[0, n)\),求\(f(i)=d\)的数量

输入输出格式

输入格式:

第一行两个整数\(n、k\),分别表示元素数量和最大属性值。
之后\(n\)行,每行三个整数\(a_i\)\(b_i\)\(c_i\),分别表示三个属性值。

输出格式:

输出\(n\)行,第\(d+1\)行表示\(f(i)=d\)\(i\)的数量。

输入输出样例

输入样例#1:

10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1

输出样例#1:

3
1
3
0
1
0
1
0
0
1

说明:

\(1\leq N\leq100000,1\leq k\leq200000\)

Ⅲ、分析问题

CDQ分治,有国家队某巨佬发明(仿佛是插头dp的论文作者???),主要用于解决带修改,查询,可排序序列的一系列问题,仅可支持离线操作
CDQ分治的主要步骤有以下几点:
1、读入(废话)
1、将已经读入好的数据按照某关键字排序
2、设当前区间为\([l,r]\),递归处理左区间\([l,mid]\)和右区间\([mid+1,r]\),计算左区间的修改操作对右区间的影响(一般用树状数组等数据结构维护)
3、清除数据结构内的修改数据
本题又叫三维偏序问题,是CDQ分治的经典题型
先按照第一维(即\(a_i\))排序,这样就将问题转化到了二维
设当前区间为\([l,r]\)
\([l,mid]\)\([mid+1,r]\)分别按照第二维排序,此时在左区间中的\(a\)均小于有区间中的\(a\)(保证第一维),设左区间已访问到\(pl\),右区间已访问到\(pr\)\((l\leq pl\leq mid,mid+1\leq pr\leq r)\)
\(b[pl]<=b[pr]\)时(保证第二维),即将\(pl\)点的\(c\)值加入树状数组
统计比\(pr\)点的\(c\)值小或等于的点的数量(保证第三维)
详见代码

#include<bits/stdc++.h>
#define ll long long
#define INF 2147483647
#define mem(i,j) memset(i,j,sizeof(i))
#define F(i,j,n) for(register int i=j;i<=n;i++)
#define lowbit(i) i&(-i)//树状数组
using namespace std;
struct hahaha{
	int x,y,z,ans,cnt;//x,y,z分别对应a,b,c;ans表示题目中的f(i),即三维都小于等于i的数量,cnt表示x,y,z相等的点的数量,若只出现一次,则cnt=1
}f[100010],s[100010];//f为输入数据,s为处理后数据
int nn,n,m,ans[100010],c[200010];//c是树状数组上的点,ans为最终答案
inline int read(){
	int datta=0;char chchc=getchar();bool okoko=0;
	while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();}
	while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();}
	return okoko?-datta:datta;
}
inline bool cmpx(hahaha a,hahaha b){//以x为第一关键字排序
	return a.x==b.x?a.y==b.y?a.z<b.z:a.y<b.y:a.x<b.x;
}
inline bool cmpy(hahaha a,hahaha b){//以y为第一关键字排序
	return a.y==b.y?a.z<b.z:a.y<b.y;
}
inline void add(int x,int v){//树状数组修改
	for(int i=x;i<=m;i+=lowbit(i))
		c[i]+=v;
}
inline int ask(int x){//树状数组查询
	int res=0;
	for(int i=x;i;i-=lowbit(i))
		res+=c[i];
	return res;
}
class CDQ_DC{//之所以用class写是为了装逼
	private:
	public:
	inline void CDQ(int l,int r){
		if(l==r)//边界条件
			return ;
		int mid=(l+r)>>1;
		CDQ(l,mid);
		CDQ(mid+1,r);//递归处理左右区间
		sort(s+l,s+mid+1,cmpy);
		sort(s+mid+1,s+r+1,cmpy);//按y排序
		int pl=l,pr=mid+1;
		while(pr<=r){
			while(pl<=mid&&s[pl].y<=s[pr].y)
				add(s[pl].z,s[pl].cnt),pl++;//加点
			s[pr].ans+=ask(s[pr].z);//处理pr的ans
			pr++;
		}
		F(i,l,pl-1)
			add(s[i].z,-s[i].cnt);//清空树状数组
	}
}C;
int main(){
	nn=read();m=read();
	F(i,1,nn)
		f[i].x=read(),f[i].y=read(),f[i].z=read();
	sort(f+1,f+nn+1,cmpx);//按x排序
	int ct=0;
	F(i,1,nn){
		ct++;
		if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y||f[i].z!=f[i+1].z){
			s[++n]=f[i];
			s[n].cnt=ct;//处理数据
			ct=0;
		}
	}
	C.CDQ(1,n);
	F(i,1,n)
		ans[s[i].ans+s[i].cnt-1]+=s[i].cnt;//处理最后答案
	F(i,0,nn-1)
		printf("%d\n",ans[i]);
	return 0;
}
posted @ 2019-01-31 18:44  hzf29721  阅读(769)  评论(1编辑  收藏  举报