CDQ分治学习总结

学习博客:

https://www.cnblogs.com/LMCC1108/category/1444281.html 来自未来“图灵奖”获得者潘武灵

https://www.cnblogs.com/mlystdcall/p/6219421.html  来自一看就知道是大佬的__stdcall

拙见:首先,在此膜一发算法发明者---陈丹琦小姐姐,%%%%%%%%。然后,开始口胡拙见。分治,顾名思义分而治之。普通的分治就是先把区间分为左右两个区间(),分别处理后(),再将两个区间合并,再去处理更大的区间。普通的分治要求左右区间没有影响,如果左右区间有影响,怎么办?这就是CDQ分治能解决的啦。CDQ分治总是考虑左边区间对右边区间的影响,也就是每次计算左边区间对右边区间的贡献。其实,本质上解决的是多维偏序问题,只会二维、三维。

通常情况下,做题套路如下:

1、首先我们把问题转化为多维偏序问题(即含有若干元素的有序元组的大小问题)。

2、然后,将第一维排序(多数问题默认第一维为时间,这样我们就可以不排序。)。

3、接着,将第二维归并排序的同时,将左边区间的贡献加给右边区间。

4、最后,将排好序的区间保存并重新赋值给原数组,以便操作更大的区间。

注意点:

1、一定要注意只算左边区间对右边区间的影响

2、处理完区间后,一定要删除之前统计的影响。

3、注意先更新,后查询之类的优先级问题。

缺点:只能离线查询,出题人要想强制在线,CDQ分治就没法搞了。

例题1、OpenJ_Bailian - 2299 Ultra-QuickSort

思路:经典的求逆序对个数问题。用树状数组再简单不过。现在用CDQ分治做。按套路来:

1、首先,(pos,val)表示为以pos为下标的数的值为val。现有左区间(pos1,val1)、右区间(pos2,val2),问题转化为求pos1<pos2,val1>val2的逆序对有多少。

2、按第一维pos从小到大排序,题目给的顺序便是,不用排。

3、将第二维val进行归并排序,现在考虑左边区间对右边区间的影响。毫无疑问,整个区间的数已按pos从小到大排序,又因为已经对左边区间内部,右边区间内部的数按val从小到大排序。假设当前处理的区间为[l,r],当左边区间的某个val(假设下标为ll)大于右边区间的某个val(rr),那么对右边的影响也就是逆序对数增加m-ll+1。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e6+10;
int a[N],n,b[N],ll,rr;
long long ans;
void solve(int l,int r)
{
	if(l==r) return ;
	int m=(l+r)>>1;
	solve(l,m),solve(m+1,r);
	ll=l,rr=m+1;
	int pos=l;
	while(ll<=m&&rr<=r)
		if(a[ll]<=a[rr])
			b[pos++]=a[ll++];
		else 
			b[pos++]=a[rr++],ans+=(m-ll+1);
	while(ll<=m)
		b[pos++]=a[ll++];
	while(rr<=r)
		b[pos++]=a[rr++];
	for(int i=l;i<=r;i++)
		a[i]=b[i];
	return ;
}
int main(void)
{
	while(~scanf("%d",&n)&&n)
	{
		ans=0;
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		solve(1,n);
		printf("%lld\n",ans);	
	}
	return 0;
}

例题2、HDU - 1166 敌兵布阵

题意:n个营地,初始时都有若干兵。有3中操作,第一种往某个营地加x个兵,第二种从某个营地减少x个兵,第三种,询问当前x到y营地总共有多少个兵。

思路:线段树做起来so easy。考虑CDQ分治,首先,查询可以拆为两个前缀和查询,之后便是套路。CDQ套路:

1、首先,(pos,op,val)表示为op种类的操作。

op==1:将pos上的值加上val。

op==2:表示第val个查询要减去pos的前缀和。

op==3:表示第val个查询要加上pos的前缀和。

现有左区间(pos1,op1,val1)、右区间(pos2,op2,val2),问题转化为当pos1<pos2,op1==1,op2>=2时,求pos1的前缀和的二维偏序问题。

2、将第一维时间从小到大排序,输入时默认有序,不用排。

3、将第二维pos进行归并排序,因为是前缀和,肯定是考虑左边区间对右边区间的影响,也就是前面的前缀和对后面前缀和的影响。现在考虑左边区间对右边区间的影响。归并排序的时候,如果左边区间的pos小于右边区间的pos,那么就更新左边区间前缀和;否则就更新右边区间的查询这里注意如果位置相等,先更新,后查询

PS:至于初始时有的兵,将他们转化为op为1的操作。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+10;
const int M = 1e6+10;
struct node
{
	int op,pos;
	ll val;
	node(){}
	node(int op,int pos,ll val):op(op),pos(pos),val(val){}
	friend bool operator <(node a,node b)
	{
		if(a.pos==b.pos) return a.op<b.op;
		else return a.pos<b.pos;
	}
}q[N],b[N];
int n,id,num;
ll ans[N],x,y;
char s[10];
void cdq(int l,int r)
{
	if(l==r)
		return ;
	int m;ll sum=0;
	m=(l+r)>>1;
	cdq(l,m),cdq(m+1,r);
	int L=l,R=m+1,i=l;
	while(L<=m&&R<=r)
	{
		if(q[L]<q[R])
		{
			if(q[L].op==1)
				sum+=q[L].val;
			b[i++]=q[L++];
		}
		else
		{
			if(q[R].op==2)
				ans[q[R].val]-=sum;
			else if(q[R].op==3)
				ans[q[R].val]+=sum;
			b[i++]=q[R++];	
		}					
	}
	while(L<=m) b[i++]=q[L++];
	while(R<=r)
	{
		if(q[R].op==2)
			ans[q[R].val]-=sum;
		else if(q[R].op==3)
			ans[q[R].val]+=sum;
		b[i++]=q[R++];
	} 
	for(int i=l;i<=r;i++)
		q[i]=b[i];
}
int main(void)
{
	int t,tt=0;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		id=n;
		for(int i=1;i<=n;i++)
			scanf("%lld",&q[i].val),q[i].op=1,q[i].pos=i,ans[i]=0;
		num=0;
		while(~scanf("%s",s+1)&&s[1]!='E')
		{
			scanf("%lld%lld",&x,&y);
			if(s[1]=='Q')
			{
				++num;
				q[++id]=node(2,x-1,num);
				q[++id]=node(3,y,num);	
			}
			else if(s[1]=='A')
				q[++id]=node(1,x,y);
			else 
				q[++id]=node(1,x,-y); 
		}
		for(int i=1;i<=num;i++)
			ans[i]=0;
		cdq(1,id);
		printf("Case %d:\n",++tt);
		for(int i=1;i<=num;i++)
			printf("%lld\n",ans[i]);
			
	}
	return 0;
}

例3、HYSBZ - 1935 Tree 园丁的烦恼

题意:二维平面上,有n棵树。m个询问,每个询问给出平面上一个矩形的左下角和右上角,问这个矩形内有多少棵树?

思路:首先,很容易就能想到二维前缀和,假设给定矩形为(x1,y1)、(x2,y2),那么根据容斥原理答案显然为

mp[x2][y2]-mp[x1-1][y2]-mp[x2][y1-1]+mp[x1-1][y1-1],我们cdq分治的时候将x归并排序,由于是前缀和始终考虑较小的x对较大的x的影响,然后用树状数组维护y的前缀和,合起来就好了。接下来我们按套路走:

1、首先,(x,y,op,val)表示为op种类的操作。

op==1:将(x,y)上的值加上1。

op==2:表示第val个查询要减去/加上(x,y)的前缀和(这里加或减用f表示)。

现有左区间(x1,y1,op1,val1)、右区间(x2,y2,op2,val2),问题转化为当x1<x2,op1==1,op2==2时的二维前缀和的二维偏序问题。

2、按第一维时间从小到大排序,输入时默认有序,不用排。

3、将第二维进行归并排序,因为是前缀和,肯定是考虑左边区间对右边区间的影响,也就是前面的前缀和对后面前缀和的影响。现在考虑左边区间对右边区间的影响。归并排序的时候,如果左边区间的x小于右边区间的x,那么就更新包含左边区间y的前缀和;否则就更新右边区间的查询这里注意如果位置相等,先更新,后查询

PS:注意每次处理完一个区间后,要清空树状数组

#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
const int N = 5e5+10;
const int M = 3e6+10;
const int maxn = 1e7+10; 
struct node
{
	int x,y,op,val,f;
	node(){}
	node(int x,int y,int op,int val,int f):x(x),y(y),op(op),val(val),f(f){}
	friend bool operator <(const node& a,const node& b)
	{
		if(a.x==b.x) return a.op<b.op;
		else return a.x<b.x;
	}
}q[M],b[M];
int sum[maxn],ans[N];
int n,m,maxy;
int lowbit(int x){ return (x&(-x)); }
void add(int x)
{
	while(x<=maxy)
	{
		sum[x]++;
		x+=lowbit(x);
	}
}
void sub(int x)
{
	while(x<=maxy)
	{
		sum[x]=0;
		x+=lowbit(x);
	}
}
int getsum(int x)
{
	int res=0;
	while(x)
	{
		res+=sum[x];
		x-=lowbit(x);
	}
        //一定要返回!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	return res;
}
void cdq(int l,int r)
{
	if(l==r) return ;
	int m=(l+r)>>1;
	cdq(l,m),cdq(m+1,r);
	int L=l,R=m+1,i=l;
	while(L<=m&&R<=r)
	{
		if(q[L]<q[R])
		{
			if(q[L].op==1)
				add(q[L].y);
			b[i++]=q[L++];
		}
		else
		{
			if(q[R].op==2)
				ans[q[R].val]+=q[R].f*getsum(q[R].y);	
			b[i++]=q[R++];
		}
	}
	while(L<=m) b[i++]=q[L++];
	while(R<=r)
	{
		if(q[R].op==2)
			ans[q[R].val]+=q[R].f*getsum(q[R].y);		
		b[i++]=q[R++];
	}
	for(i=l;i<=r;i++)
	{
		if(b[i].op==1)
			sub(b[i].y);
		q[i]=b[i];
	}
}

int main(void)
{
	scanf("%d%d",&n,&m);
	int x1,y1,x2,y2,id;
	maxy=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&x1,&y1);
		x1++,y1++;
		q[i]=node(x1,y1,1,1,0);
		if(y1>maxy) maxy=y1;
	}
	id=n;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		x1++,x2++,y1++,y2++;
		q[++id]=node(x2,y2,2,i,1);
		q[++id]=node(x2,y1-1,2,i,-1);
		q[++id]=node(x1-1,y2,2,i,-1);
		q[++id]=node(x1-1,y1-1,2,i,1);		
		if(y1>maxy) maxy=y1;
		if(y2>maxy) maxy=y2;
	}	
	cdq(1,id);
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
	return 0;	
} 

例4、2019南京网络赛 A. The beautiful values of the palace

题意:和上一个题几乎一模一样,就是矩阵上的值求起来麻烦些。

思路:请参考上一题。

PS:不用long long会错,我的实现有问题。

#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int N = 1e6+10;
struct node
{
	int x,y,op,val,f;
	node(){}
	node(int x,int y,int op,int val,int f):x(x),y(y),op(op),val(val),f(f){}
	friend bool operator <(const node& a,const node& b)
	{
		if(a.x==b.x) return a.op<b.op;
		else return a.x<b.x;
	}
}q[N],b[N];
int n,m,p,maxy,id;
ll ans[N],sum[N];
ll getval(int x,int y)
{
	int xx=x,yy=y;
	ll ans=0,res=0;
    ll layer=min(min(x,y),n-max(x,y)+1);
    if((layer<<1)>n) ans=1LL*n*n;
    else
    {
	    ll st=n*n-(n-(layer<<1)+2)*(n-(layer<<1)+2)+1;
	    if (x>=y) ans=st+((n-layer)<<1)+2-x-y;
	    else ans=st+((n-(layer<<1)+1)<<1)+x+y-(layer<<1);    	
	}
	while(ans)
	{
		res+=(ans%10);
		ans/=10;	
	}	
	return res;
} 
int lowbit(int x){ return (x&(-x)); }
void add(int x,int val)
{
	while(x<=maxy)
	{
		sum[x]+=val;
		x+=lowbit(x);
	}
}
void sub(int x)
{
	while(x<=maxy)
	{
		sum[x]=0;
		x+=lowbit(x);
	}
}
ll getsum(int x)
{
	int xx=x;
	ll res=0;
	while(x)
	{
		res+=sum[x];
		x-=lowbit(x);	
	}
        //一定要返回!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	return res;
}
void cdq(int l,int r)
{
	if(l==r) return ;
	int m=(l+r)>>1;
	cdq(l,m),cdq(m+1,r);
	int L=l,R=m+1,i=l;
	while(L<=m&&R<=r)
	{
		if(q[L]<q[R])
		{
			if(q[L].op==1)
				add(q[L].y,q[L].val);
			b[i++]=q[L++];
		}
		else
		{
			if(q[R].op==2)
				ans[q[R].val]+=getsum(q[R].y)*q[R].f;
			b[i++]=q[R++];
		}
	}
	while(L<=m) b[i++]=q[L++];
	
	while(R<=r)
	{
		if(q[R].op==2)
			ans[q[R].val]+=getsum(q[R].y)*q[R].f;
		b[i++]=q[R++];	
	}
	for(i=l;i<=r;i++)
	{
		if(b[i].op==1)
			sub(b[i].y);
		q[i]=b[i];
	}
}
signed main(void)
{
	int t,x1,y1,x2,y2;
	scanf("%lld",&t);
	while(t--)
	{
		scanf("%lld%lld%lld",&n,&m,&p);
		id=m,maxy=0;	
		for(int i=1;i<=m;i++)
		{
			scanf("%lld%lld",&x1,&y1);
			q[i]=node(x1,y1,1,getval(x1,y1),1);
			if(y1>maxy) maxy=y1;
		}
		for(int i=1;i<=p;i++)
		{
			ans[i]=0;
			scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);		
			q[++id]=node(x2,y2,2,i,1);
			q[++id]=node(x1-1,y2,2,i,-1);
			q[++id]=node(x2,y1-1,2,i,-1);				
			q[++id]=node(x1-1,y1-1,2,i,1);		
			if(y1>maxy) maxy=y1;
			if(y2>maxy) maxy=y2;
		}
		cdq(1,id);
		for(int i=1;i<=p;i++)
			printf("%lld\n",ans[i]);
	}
	return 0;
}

例5、HYSBZ - 3262 陌上花开

题意:有n朵花,每朵花有3种属性(s,c,m),给出属性的最大值k。现在给n朵花划分等级,一朵花的等级为所有花中s>=s1,c>=c1,m>=m1的个数。这很明显的三维偏序问题,要注意的是,s、c、m都相等的花,等级相同,那么我们要去重把们归为一类,剩下的就按套路走。

1、首先,(s,c,m,id,val)表示为属性为(s,c,m)的花为低id种,有val个。现有左区间(s1,c1,m1,id1,val1)、右区间(s2,c2,m2,id2,val2),问题转化为当s1<s2,c1<c2时,求m1前缀和的二维偏序问题。

2、按第一维s从小到大排序。

3、将第二维c进行归并排序,因为是前缀和,肯定是考虑左边区间对右边区间的影响,也就是前面的前缀和对后面前缀和的影响。现在考虑左边区间对右边区间的影响。归并排序的时候,如果左边区间的c小于等于右边区间的c,那么就更新包含左边区间m的前缀和;否则就更新右边区间的查询

PS:注意每次处理完一个区间后,要清空树状数组

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
struct node
{
	int s,c,m,id,val;
}a[N],b[N];
int n,k;
int le[N],ans[N],sum[N<<1];
bool cmp(const node& a,const node& b)
{
	if(a.s==b.s)
	{
		if(a.c==b.c)
			return a.m<b.m;
		else return a.c<b.c;
	}
	return a.s<b.s;
}
int lowbit(int x){ return (x&(-x));}
void add(int x,int val)
{
	while(x<=k)
	{
		sum[x]+=val;
		x+=lowbit(x);
	}
}
void sub(int x)
{
	while(x<=k)
	{
		sum[x]=0;
		x+=lowbit(x);
	}		
}
int getsum(int x)
{
	int res=0;
	while(x)
	{
		res+=sum[x];
		x-=lowbit(x);
	}
	//记得返回!!!!!!!!!!!!!!!!!!!!!! 
	return res; 
}
void cdq(int l,int r)
{
	if(l==r)
	{
		le[a[l].id]+=a[l].val-1;
		return ;	
	} 
	int m=(l+r)>>1;
	cdq(l,m),cdq(m+1,r);
	int L=l,R=m+1,i=l;
	while(L<=m&&R<=r)
	{
		if(a[L].c<=a[R].c)
		{
			add(a[L].m,a[L].val);
			b[i++]=a[L++];
		}
		else
		{
			le[a[R].id]+=getsum(a[R].m);
			b[i++]=a[R++];
		} 		
	}
	while(L<=m) b[i++]=a[L++];
	
	while(R<=r)
	{
		le[a[R].id]+=getsum(a[R].m);
		b[i++]=a[R++];	
	} 
	
	for(i=l;i<=r;i++)
	{
		sub(a[i].m);
		a[i]=b[i];
	}		
}
bool equal(node a,node b){ return a.s==b.s&&a.c==b.c&&a.m==b.m; }
int main(void)
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d%d%d",&a[i].s,&a[i].c,&a[i].m),a[i].val=1;
	sort(a+1,a+1+n,cmp);
	int id=1;
	a[1].id=1;
	for(int i=2;i<=n;i++)
		if(equal(a[i],a[i-1]))
			a[id].val++;
		else
			a[++id]=a[i],a[id].id=id;
					
	cdq(1,id);
	for(int i=1;i<=id;i++)
		ans[le[a[i].id]]+=a[i].val;	
	for(int i=0;i<n;i++)
		printf("%d\n",ans[i]);
	return 0;	
}  

总结:大体上,我觉得我总结的套路够用,再难的恐怕也没有能力去解决了。注意树状数组一定要返回,错了好几次了。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值