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;
}

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

另外一道题就是一道卡常猥琐题[Violet]天使玩偶/SJY摆棋子。这道题首先要把绝对值的式子拆开,发现可以分类讨论:如果它在当前点的左下角,求最短距离就变成了求所有在它左下方的点中横纵坐标之和最大的那个点的值。很明显,左下角这个东西就是二维偏序,再加上个时间的有序性,这就是一个三维偏序问题,套模板即可。至于在其它方向上的点,可以通过对称把它们对称到左下角,实现上就是跑4次cdq,每跑一次就重新搞一次点坐标,更新答案即可。

这道题卡常,本蒟蒻也是吸了氧之后才过掉的。另外,这道题还有一个点就是树状数组每个点的初值应该是负无穷而不是0(调了好久才猛然发现这个问题),还有就是数组别开小了,你仔细看它的数据规模……

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define zczc
using namespace std;
const int N=500010;
const int M=1100010;
const int maxm=M*2;
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;
}
inline int max(int s1,int s2){
	return s1<s2?s2:s1; 
}
inline int min(int s1,int s2){
	return s1<s2?s1:s2;
}
inline void check(int &s1,int s2){
	if(s1>s2)s1=s2;return;
}

int m,n,maxx,maxy,ans[N<<1];
struct node{
	int op,t,x,y,ans,num;
}a[N<<1],b[N<<1],fir[N<<1];
inline bool cmp(node s1,node s2){
	return s1.t<s2.t;
}

#define lowbit (wh&-wh)
int t[M],maxn;
inline void init(int wh){
	for(;wh<=maxn;wh+=lowbit)t[wh]=-maxm;
	return;
}
inline void change(int wh,int val){
	for(;wh<=maxn;wh+=lowbit){
		t[wh]=max(t[wh],val);
	}
	return;
}
inline int work(int wh){
	int an=-maxm;
	for(;wh;wh-=lowbit)an=max(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].x<=a[j].x)){
			b[k]=a[i++];
			if(b[k].op==1)change(b[k].y,b[k].x+b[k].y);
		}
		else{
			b[k]=a[j++];
			if(b[k].op==2){
				check(ans[b[k].num],b[k].x+b[k].y-work(b[k].y));
			}
		}
	}
	for(int i=l;i<=r;i++){
		a[i]=b[i];
		init(a[i].y);
	}
	return;
}
void solve(bool wh){
	memset(t,0xcf,sizeof(t));
	for(int i=1;i<=m;i++)a[i].x++,a[i].y++;
	cdq(1,m);
	return;
}

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	
	memset(ans,0x7f,sizeof(ans));
	
	read(m);read(n);
	for(int i=1;i<=m;i++){
		fir[i].op=1;
		fir[i].t=0;
		read(fir[i].x);
		read(fir[i].y);
		maxx=max(maxx,fir[i].x);
		maxy=max(maxy,fir[i].y);
	}
	for(int i=1;i<=n;i++){
		int now=m+i;
		fir[now].t=i;
		fir[now].num=i;
		read(fir[now].op);
		read(fir[now].x);
		read(fir[now].y);
		maxx=max(maxx,fir[now].x);
		maxy=max(maxy,fir[now].y);
	}
	m+=n;
	maxn=maxx+maxy+100;
	
	for(int i=1;i<=m;i++)a[i]=fir[i];
	solve(false);
	for(int i=1;i<=m;i++){
		a[i]=fir[i];
		a[i].x=maxx-fir[i].x;
		a[i].y=maxy-fir[i].y;
	}
	solve(true);
	for(int i=1;i<=m;i++){
		a[i]=fir[i];
		a[i].x=maxx-fir[i].x;
	}
	solve(true);
	for(int i=1;i<=m;i++){
		a[i]=fir[i];
		a[i].y=maxy-fir[i].y;
	}
	solve(true);
	
	for(int i=1;i<=m;i++)if(ans[i]<maxm*2)printf("%d\n",ans[i]);
	
	return 0;
}

cdq基础应该就这些内容了,如果再有cdq的题的话,我想应该就不能叫做基础了对吧。

posted @ 2021-12-04 10:45  Feyn618  阅读(63)  评论(0编辑  收藏  举报