cdq分治用来解决多维偏序问题,分治时统计左区间的修改对右区间产生的影响
之所以不考虑右区间对左区间的影响,是因为通常已经通过排序消掉了一维,右区间对左区间不会产生影响
cdq分治是一种离线算法
二维偏序
将其中一维排序,消掉一维的影响,另一维通过cdq分治处理
1.逆序对问题
计算数列中的逆序对个数
逆序对是指(i<j)并且(a_i>a_j)的数对,逆序对可以在归并排序的合并过程中计算,下标所在的维度是默认有序的,如果合并时左区间中的(a[i])大于右区间中的(a[j]),则对于下标为(j)的元素来说,左区间对它产生的影响是逆序对数增加((mid-i+1))
const int maxn=100010;
int n,k,a[maxn],b[maxn];
LL 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,k=l;
while(i<=mid && j<=r){
if(a[i]<=a[j]) b[k++]=a[i++];
else{
cnt+=mid-i+1;
b[k++]=a[j++];
}
}
while(i<=mid) b[k++]=a[i++];
while(j<=r) b[k++]=a[j++];
for(i=l;i<=r;i++) a[i]=b[i];
}
2.单点修改,区间查询问题
设对于一个数列有两种操作:
1 x y 表示将下标为x的元素加上y
2 x y 表示计算区间[x,y]之内的元素和
树状数组单点修改,区间求和模板,也可以通过cdq分治处理
设二维偏序(a,b),其中a是操作时间,b是操作位置
时间作为默认有序的第一维度,就是将所有操作按照进行的顺序作为数组下标,cdq分治处理第二维度
将每个查询操作拆分成两个元素:l-1的前缀和以及r的前缀和
每个元素包含三个变量:
type:操作类型,1表示修改,2表示查询l-1的前缀和,3表示查询r的前缀和
pos:操作位置
val:修改操作表示增加的值,查询操作表示查询编号
const int maxn=50010,maxm=50010;
int n,m,ans[maxm];
struct node{
int type,pos,val;
bool operator < (const struct node &t)const{
if(pos!=t.pos) return pos<t.pos;
return type<t.type;
}
}a[2*maxm+maxn],b[2*maxm+maxn];
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;
LL sum=0;
for(int k=l;k<=r;k++){
if((i<=mid && j<=r && a[i]<a[j]) || j>r){
if(a[i].type==1){
sum+=a[i].val;
}
b[k]=a[i++];
}
else{
if(a[j].type==2){
ans[a[j].val]-=sum;
}
else if(a[j].type==3){
ans[a[j].val]+=sum;
}
b[k]=a[j++];
}
}
for(int k=l;k<=r;k++) a[k]=b[k];
}
void solve(){
scanf("%d %d",&n,&m);
int tot=1,totq=1;
for(int i=1;i<=n;i++){
scanf("%d",&a[tot].val);
a[tot].pos=i;
a[tot++].type=1;
}
for(int i=1;i<=m;i++){
int type;
scanf("%d",&type);
if(type==1){
int pos,val;
scanf("%d %d",&pos,&val);
a[tot].pos=pos;
a[tot].val=val;
a[tot++].type=1;
}
else{
int l,r;
scanf("%d %d",&l,&r);
a[tot].pos=l-1;
a[tot].val=totq;
a[tot++].type=2;
a[tot].pos=r;
a[tot].val=totq++;
a[tot++].type=3;
}
}
cdq(1,tot-1);
for(int i=1;i<totq;i++) printf("%d
",ans[i]);
}
三维偏序
给出一些三维坐标系下点的坐标(x,y,z),计算对于每一个点(i),满足(x_igeq x_j)并且(y_igeq y_j)并且(z_igeq z_j)的点(j)的个数
通过按照(x)值排序减掉一维,cdq分治减掉第二维,树状数组统计符合条件的第三维的点
在cdq分治的合并过程中,按照(y)值从小到大排序,这样在统计左区间对右区间的贡献时,从左到右扫描右区间,左区间从最左端开始向右走,不回溯,将左区间中(y)值小于等于当前右区间的点的(z)值直接加入树状数组,每次统计树状数组中小于等于当前右区间中点的(z)值的点的个数。所有右区间中的点都统计完成之后,将树状数组中的所有的(z)值清空
由于所有点初始都是按(x)值排序的,所以左区间中点的(x)值一定符合条件,而合并的时候又按照(y)值排序,所以左区间中所有(y)值符合条件的点都已经被扫描,所以直接计算树状数组中符合条件的(z)值的个数,就是满足条件的左区间中的点的个数
模板题:hdu5618 Jam's problem again
#include<iostream>
#include<cstdio>
#include<vector>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<cstring>
#include<string>
#include<sstream>
#include<cmath>
#include<ctime>
#include<algorithm>
#define LL long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define pi acos(-1.0)
#define eps 1e-6
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=100010;
int T,n,bit[maxn],ans[maxn];
struct Point{
int x,y,z,id,sum;
Point(){}
Point(int x,int y):x(x),y(y){}
bool operator < (const Point &t)const{
if(x!=t.x) return x<t.x;
if(y!=t.y) return y<t.y;
return z<t.z;
}
bool operator == (const Point &t)const{
return x==t.x && y==t.y && z==t.z;
}
}a[maxn],b[maxn];
void change(int x,int v){
while(x<=100000){
bit[x]+=v;
x+=lowbit(x);
}
}
int query(int x){
int sum=0;
while(x){
sum+=bit[x];
x-=lowbit(x);
}
return sum;
}
void cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
cdq(l,mid);
cdq(mid+1,r);
int j=l;
for(int i=mid+1;i<=r;i++){
for(;j<=mid && a[j].y<=a[i].y;j++) change(a[j].z,1);
a[i].sum+=query(a[i].z);
}
for(int i=l;i<j;i++) change(a[i].z,-1);
int i=l;
j=mid+1;
for(int k=l;k<=r;k++){
if(i>mid) b[k]=a[j++];
else if(j>r) b[k]=a[i++];
else if(a[i].y<a[j].y) b[k]=a[i++];
else b[k]=a[j++];
}
for(int k=l;k<=r;k++) a[k]=b[k];
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d %d %d",&a[i].x,&a[i].y,&a[i].z);
a[i].id=i;
a[i].sum=0;
}
sort(a+1,a+1+n);
memset(bit,0,sizeof(bit));
cdq(1,n);
sort(a+1,a+1+n);
for(int i=1;i<=n;){
int j=i+1;
int tmp=a[i].sum;
for(;j<=n && a[i]==a[j];j++) tmp=max(tmp,a[j].sum);
for(int k=i;k<j;k++) ans[a[k].id]=tmp;
i=j;
}
for(int i=1;i<=n;i++){
printf("%d
",ans[i]);
}
}
return 0;
}