cdq分治

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\n",ans[i]);
}

三维偏序
给出一些三维坐标系下点的坐标\(x,y,z\),计算对于每一个点\(i\),满足\(x_i\geq x_j\)并且\(y_i\geq y_j\)并且\(z_i\geq 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\n",ans[i]);
        }
    }
    return 0;
}
posted @ 2020-08-24 20:29  fxq1304  阅读(179)  评论(0编辑  收藏  举报