CDQ分治初步

今日初学cdq分治,特来写个小小的总结。

首先看这个名字就知道和分治有关,前缀名其实没那么重要。一上午学习下来个人感觉就是这玩意不就是在归并上加了个外挂吗……

直接看最模板的题目,陌上花开。

这名字确实很诗意,难得。本想着题面里可能也会有类似的表述,然鹅并没有。终究是错付了。

题意很简单,求三维偏序。

按照cdq的方法,首先直接对第一维进行排序,这就说明了只有前面的点可能更新到后面的点,这就可以搞分治了;然后发现,既然是分治用左边更新右边,那么元素们就只有左右之分,也就是说左区间和右区间两个子区间内的元素内部顺序便不再重要(详情请看《乌合之众》和《*分子》),便可以考虑对它们的第二维进行动态的排序(这和归并是一模一样的),再最后以第三维的权值为桶用树状数组查询答案即可。复杂度\(O(NlogN)\)。

这就是cdq的基本思想,基于部分排序达到减少维度的效果。当然这其中有一些需要注意的地方:

  • 最后树状数组清空为下一次做准备时尽量不用传统的memset,容易超时(或者说肯定会超时,因为memset会让复杂度达到 \(O(N^2)\) 左右),特别惨,还不如暴力。
  • 关于顺序需要具体情况具体分析。这里的顺序是指先处理右区间还是先合并。这道题是先处理右区间,但对于某些问题就必须先合并,比如不那么经典的LIS计数问题就应当先合并再处理右区间。具体原因仍在思考,以后再总结罢。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define zczc
using namespace std;
const int N=100010;
const int M=200010;
inline void read(int &wh){
	wh=0;int f=1;char w=getchar();
	while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
	while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
	wh*=f;return;
}

int m,n,cnt,ans[N];
struct no{
	int x,y,z,num,ans;
}a[N],b[N];//b为归并排序的辅助数组 
bool operator ==(no s1,no s2){
	return s1.x==s2.x&&s1.y==s2.y&&s1.z==s2.z;
}
inline bool cmp1(no s1,no s2){
	if(s1.x!=s2.x)return s1.x<s2.x;
	else if(s1.y!=s2.y)return s1.y<s2.y;
	else return s1.z<s2.z;
}

#define lowbit (wh&-wh)
int t[M];
inline void clear(){
	memset(t,0,sizeof(t));
	return;
}
inline void init(int wh){
	for(;wh<=n;wh+=lowbit)t[wh]=0;
}
inline void change(int wh,int val){
	for(;wh<=n;wh+=lowbit)t[wh]+=val;
	return;
}
inline int work(int wh){
	int an=0;
	for(;wh;wh-=lowbit)an+=t[wh];
	return an;
}
#undef lowbit
 
void cdq(int l,int r){
	if(l==r)return;
	int mid=l+r>>1;
	cdq(l,mid);cdq(mid+1,r);
	
	for(int i=l,j=mid+1,k=l;k<=r;k++){
		if(j>r||(i<=mid&&a[i].y<=a[j].y)){
			b[k]=a[i++];
			change(b[k].z,b[k].num);
		}
		else{
			b[k]=a[j++];
			b[k].ans+=work(b[k].z);
		}
	}
	for(int i=l;i<=r;i++){
		a[i]=b[i];
		init(b[i].z);
	}
	
	return;
}

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(n);
	for(int i=1;i<=m;i++){
		read(a[i].x);read(a[i].y);read(a[i].z);a[i].num=1;
	}
	sort(a+1,a+m+1,cmp1);
	for(int i=1;i<=m;i++){
		if(cnt!=0&&a[cnt]==a[i])a[cnt].num++;
		else a[++cnt]=a[i];
	}
	
	cdq(1,cnt);
	for(int i=1;i<=cnt;i++)ans[a[i].ans+a[i].num-1]+=a[i].num;
	for(int i=0;i<m;i++)printf("%d\n",ans[i]);
	
	return 0;
}

双倍经验: 动态逆序对,稍微转化一下就好了。懒得写了。

上一篇:16. 多态与instanceof


下一篇:CUDA学习-计算实际线程ID