cdq分治

初略写一下cdq分治,想要更详细的可以去看别人的更优质的博客。

cdq分治的本质是用到了归并排序的,

所以不知道归并排序怎么打的人可以先去学归并排序,

先上三维偏序的cdq分治代码..  模板题传送门

#include<bits/stdc++.h>
#define lowbits(i) i&(-i) 
using namespace std;
const int N=1e5+1e4;
int n,k,m,b[N*4],c[N];
struct PP{
    int a,b,c,w,ans;
}a[N],tmp[N];
int rd(){
    int s=0,ff=1;
    char w=getchar();
    while(w<'0'||w>'9'){
        if(w=='-') ff=-1;
        w=getchar();
    }
    while(w>='0'&&w<='9'){
        s=s*10+(w-'0');
        w=getchar();
    }
    return s*ff;
}
bool cmp(PP aa,PP bb){
    return aa.a==bb.a?(aa.b==bb.b?aa.c<bb.c:aa.b<bb.b):aa.a<bb.a;
}
void Add(int x,int y){
    for(int i=x;i<=k;i+=lowbits(i)){
        b[i]+=y;
    }
}
void Del(int x){
    for(int i=x;i<=k;i+=lowbits(i)){
        b[i]=0;
    }
}
int Query(int x){
    int sum=0;
    for(int i=x;i>0;i-=lowbits(i)){    
        sum+=b[i];
    }
    return sum;
}
bool operator<(PP aa,PP bb){
    return aa.b==bb.b?aa.c<=bb.c:aa.b<bb.b;
}
void Cdq(int l,int r){    
    if(l==r){
        return ;
    }
    int mid=(l+r)>>1;
    Cdq(l,mid); Cdq(mid+1,r);
    int t=l,L=l,M=mid+1;
    while(L<=mid&&M<=r){
        if(a[L]<a[M]){
            Add(a[L].c,a[L].w);
            tmp[t++]=a[L++];
        }
        else{
            a[M].ans+=Query(a[M].c);
            tmp[t++]=a[M++];
        }
    }
    while(L<=mid){
        Add(a[L].c,a[L].w);
        tmp[t++]=a[L++];
    }
    while(M<=r){
        a[M].ans+=Query(a[M].c);
        tmp[t++]=a[M++];
    }
    for(int i=l;i<=r;i++){
        a[i]=tmp[i];
        Del(a[i].c);
    }
}
int main(){
    int minn=1e9; m=rd(); k=rd();
    for(int i=1;i<=m;i++){
        a[i]=(PP){rd(),rd(),rd(),1,0};
        minn=min(minn,a[i].c);
    } minn--; k-=minn;
    for(int i=1;i<=m;i++){
        a[i].c-=minn;
    }
    sort(a+1,a+1+m,cmp);
    for(int i=1;i<=m;i++){
        if(a[i].a==a[i-1].a&&a[i].b==a[i-1].b&&a[i].c==a[i-1].c){
            a[i].w=a[i-1].w+1; a[i-1]=(PP){1e9,1e9,1e9,0,0};
        }
    } sort(a+1,a+1+m,cmp); n=m;
    while(!a[n].w) {n--;} Cdq(1,n);
    for(int i=1;i<=n;i++){
        c[a[i].ans+a[i].w-1]+=a[i].w;
    }
    for(int i=0;i<m;i++){
        printf("%d\n",c[i]);
    }
    return 0;
}

先解释时间复杂度

因为cdq分治的过程是在归并排序中进行的,

所以cdq分治的时间复杂度为O(nlog n),

但是这题(三维偏序)需要用到sort+树状数组+cdq分治,

所以时间复杂度为O(nlog n log k)(k为最大元素值)。

这题也可以用sort+cdq分治+cdq分治,

时间复杂度为O(nlog2n)。

下面是原理

用二维偏序解释吧,这样好解释点,

(第一维是a[],第二维是b[],

求f[i]为a[i]>=a[j]&&b[i]>=b[j]的j的个数)

当然二维偏序可以用(sort+树状数组)过,

但是我们就是要用(sort+cdq分治)过,

首先用sort解决一维,使其单调排序,

然后第二维的话如图所示(图中视为第一维已经排好序了):

先是归并排序向下递归

再是归并排序向上合并(左边的指针为i,右边的指针为j)

就是在合并的时候要统计左边一块对右边一块的答案贡献,

因为左边的a是小于等于右边的a的,所以不管,

当指针i所在的数小于等于指针j所在的数,

说明在j之后的数的答案贡献都可以加上1,

用一个变量累加存过去就好了,

当然你得存下一个数一开始在数列中的位置,

代码如下:

void Cdq(int l,int r){
    if(l==r){
        return ;
    }
    int mid=(l+r)>>1;
    Cdq(l,mid); Cdq(mid+1,r);
    int t=l,i=l,j=mid+1,ss=0;
    while(i<=mid&&j<=r){
        if(b[i]<=b[j]){
            ss++,tmp[t++]=b[i++];
        }
        else{
            f[w[b[j]]]+=ss,tmp[t++]=b[j++];//w[b[j]]表示b[j]这个数在数列中的位置;
        }
    }
    while(i<=mid){
        ss++,tmp[t++]=b[i++];
    }
    while(j<=r){
        f[w[b[j]]]+=ss,tmp[t++]=b[j++];
    }
    for(int i=l;i<=r;i++){
        b[i]=tmp[i];
    }
}

所以cdq分治主要的思想就是:

分治->合并->算左边对右边的贡献

最后再强调下单用cdq分治时间复杂度是O(nlog n)的,

三维偏序中再加上树状数组才是O(nlog n log k)的。

就写到这了。哪里不明白可以评论问我。

posted @ 2019-07-24 17:31  八水L酱  阅读(272)  评论(0编辑  收藏  举报