CDQ分治最强讲解

let's start with a simple question----

传说中的逆序对

第一种方法是归并排序顺便求

int merge(int l,int r)
{
	int mid=(l+r)>>1,zz1=l,zz2=mid+1,sum=0;
	for(int i=l;i<=r;i++){
		if(zz2>r||zz1<=mid&&c[zz1]<c[zz2]) cur[i]=c[zz1++];
		else{
		cur[i]=c[zz2++];
		sum+=mid-zz1+1;
	    }
	}
	for(int i=l;i<=r;i++) c[i]=cur[i];
	return sum;
}
void solve(int l,int r){ 
    if(l<r){
	int mid=(l+r)>>1;
	solve(l,mid);
	solve(mid+1,r);
	ans+=merge(l,r);
    }
}

二是树状数组

​
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int c[N],n,ans;
int read(){
	int cnt=0;char ch=0;
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
	return cnt;
}
int lowbit(int x){return x&-x;}
int add(int pos,int val){
	for(;pos<=n;pos+=lowbit(pos))
		c[pos]+=val;
}
int getsum(int x){
	int cnt=0;
	for(;x;x-=lowbit(x))cnt+=c[x];
	return cnt;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int a=read();
		add(a,1);
		ans+=i-getsum(a);
	}
	cout<<ans;
	return 0;
}

​

将值作为下标存入树状数组,来一个查询比它小的个数,减一下就是比它大的即为逆序对

于是我们知道

树状数组跟分治在某些方面有联系

好,正式进入CDQ分治

求满足Ai<=Aj,Bi<=Bj,Ci<=Cj的数对的数目。

我们先按a排序

我们在先只考虑b

相当于求b的顺序对

再在(a,b)顺序对中找c的顺序对

b可以跟刚刚逆序对一样

c可以用树状数组

看代码吧

int lowbit(int x){return x&-x;} 
void add(int pos,int val){
	for(;pos<=k;pos+=lowbit(pos)) c[pos]+=val;
} 
int quary(int x){
	int cnt=0;
	while(x) cnt+=c[x],x-=lowbit(x); 
	return cnt;
}
void cdq(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid),cdq(mid+1,r);
	int i=l,j=mid+1;
	//先跑个逆序对之类的东西
	for(int k=l;k<=r;k++){
		if(j>r||(i<=mid&&a[i].y<=a[j].y)) add(a[i].z,a[i].cnt),b[k]=a[i++];
		else a[j].ans+=quary(a[j].z),b[k]=a[j++]; 
	} 
	for(int i=l;i<=mid;i++) add(a[i].z,-a[i].cnt);//清空树状数组
	for(int i=l;i<=r;i++) a[i]=b[i]; 
}

升级版----

维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的总权值.修改操作数M<=160000,询问数Q<=10000,W<=2000000.

第一行两个整数,S,W;其中S为矩阵初始值;W为矩阵大小

接下来每行为一下三种输入之一(不包含引号):

"1 x y a"

"2 x1 y1 x2 y2"

"3"

输入1:你需要把(x,y)(第x行第y列)的格子权值增加a

输入2:你需要求出以左上角为(x1,y1),右下角为(x2,y2)的矩阵内所有格子的权值和,并输出

输入3:表示输入结束

所有cdq分治问题

基本上都要转成前缀和处理

例如这道题(x1,y1)-(x2,y2)相当于mmap[i-1][j-1]+mmap[k][l]-mmap[i-1][l]-mmap[j-1][k]

我们将问题和修改一起放进去,然后按x排序,然后分治

先看代码吧

#include<bits/stdc++.h>
#define N 200005 
using namespace std;
struct Node{int x,y,z,pos,id;}a[N],b[N];
int ans[10005],c[N*4],w,s,n,cnt;
int read(){
	int cnt=0;char ch=0;
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
	return cnt;
} 
bool cmp(Node a,Node b){
	if(a.x==b.x){
		if(a.y==b.y) return a.pos<b.pos;
		return a.y<b.y;
	}
	return a.x<b.x;
}
int lowbit(int x){return x&-x;}
void add(int x,int val){
	for(;x<=w;x+=lowbit(x)) c[x]+=val;
}
int quary(int x){
	int q=0;
	for(;x;x-=lowbit(x)) q+=c[x];
	return q;
}
void cdq(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1,i=l,j=mid+1;
	for(int k=l;k<=r;k++){
		if(a[k].id<=mid&&a[k].pos==0) add(a[k].y,a[k].z);//为修改 
		if(a[k].id>mid&&a[k].pos) ans[a[k].pos]+=quary(a[k].y)*a[k].z;
	}
	for(int k=l;k<=r;k++) 
		if(a[k].id<=mid&&a[k].pos==0) add(a[k].y,-a[k].z);
	for(int k=l;k<=r;k++){
		if(a[k].id<=mid) b[i++]=a[k];
		else b[j++]=a[k];
	}
	for(int k=l;k<=r;k++) a[k]=b[k];
	cdq(l,mid),cdq(mid+1,r);
}
int main()
{
	//freopen("1.in","r",stdin);
	s=read(),w=read();
	while(1){
		int op=read();
		if(op==1){
			int x=read(),y=read(),z=read();
			a[++n]=(Node){x,y,z,0,n};
		}
		else if(op==2){
			int x1=read()-1,y1=read()-1,x2=read(),y2=read();
			ans[++cnt]=(x2-x1)*(y2-y1)*s; 
			a[++n]=(Node){x1,y1,1,cnt,n}; 
			a[++n]=(Node){x1,y2,-1,cnt,n}; 
			a[++n]=(Node){x2,y1,-1,cnt,n}; 
			a[++n]=(Node){x2,y2,1,cnt,n}; 
		} 
		else break;
	}
	sort(a+1,a+n+1,cmp);
	cdq(1,n);
	for(int i=1;i<=cnt;i++) cout<<ans[i]<<endl;
	return 0;
}

前一半的修改一定对后一半的查询有影响

我们相当于找(x,y,time)的顺序对

总得来说

cdq分治一般与树状数组相配合

cdq分治能保证查询一定在修改之后

一维排序,二维分治,三维树状数组

猜你喜欢

转载自blog.csdn.net/sslz_fsy/article/details/81292590