CDQ分治以及应用&&BZOJ1935

CDQ分治是一种用左边区间来进行对右半边区间修改的分治。
最简单的就是求逆序对(二位偏序),我们二分区间[l,r],然后用区间[l,mid]里的值去更新[mid+1,r]。这就是最简单的CDQ分治的一种应用。
那么我们可以扩展到三位偏序问题,下面有一道模板题:BZOJ3262

Description

有n朵花,每朵花有三个属性:花形(s)、颜色(c)、气味(m),用三个整数表示。
现在要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量。
定义一朵花A比另一朵花B要美丽,当且仅Sa>=Sb,Ca>=Cb,Ma>=Mb。
显然,两朵花可能有同样的属性。需要统计出评出每个等级的花的数量。
Input

第一行为N,K (1 <= N <= 100,000, 1 <= K <= 200,000 ), 分别表示花的数量和最大属性值。
以下N行,每行三个整数si, ci, mi (1 <= si, ci, mi <= K),表示第i朵花的属性
Output

包含N行,分别表示评级为0…N-1的每级花的数量。

Sample Input

10 3

3 3 3

2 3 3

2 3 1

3 1 1

3 1 2

1 3 1

1 1 2

1 2 2

1 3 2

1 2 1
Sample Output

3

1

3

0

1

0

1

0

0

1


这就是裸的三维偏序问题,给定我们三元组(x,y,z)求有多少三元组集满足xi>xj,yi>yj,zi>zj
我们可以这样做,先将x,y,z按照x从小到大sort一遍,这样就解决了第一维。然后我们对第二维进行CDQ分治。然而还剩下一维,我们直接用树状数组维护即可。
具体来说,我们在二维上进行CDQ分治,比如分治到区间[l,r],则我们将区间[l,mid]中小于二分值的点插入树状数组,然后将大于二分值的点进行求值,这样就完成了CDQ分治。
#include<bits/stdc++.h>
#define MAXN 200005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,mx,cnt,dfn,tot[MAXN],sum[MAXN],bel[MAXN],ans[MAXN],out[MAXN];
struct node{
    int x,y,z,id;
}F[MAXN],Ne[MAXN],lx[MAXN],rx[MAXN];
int cmp(node a,node b){return a.x==b.x?(a.y==b.y?a.z<b.z:a.y<b.y):a.x<b.x;}
int same(int x,int y){
    if(F[x].x==F[y].x&&F[x].y==F[y].y&&F[x].z==F[y].z) return 1;
    else return 0;
}
int lowbit(int x){return x&-x;}
void add(int x,int ad){
    while(x<=mx){
        if(bel[x]!=dfn) sum[x]=0,bel[x]=dfn;
        sum[x]+=ad;x+=lowbit(x);
    }
}
int getsum(int x){
    int res=0;
    while(x){
        if(dfn==bel[x]) res+=sum[x];
        x-=lowbit(x);
    }
    return res;
}
void cdq(int L,int R,int l,int r){
    dfn++;
    int mid=(l+r)>>1,lt=0,rt=0;
    for(int i=L;i<=R;i++){
        if(l==r){
            ans[Ne[i].id]+=getsum(Ne[i].z);
            add(Ne[i].z,tot[Ne[i].id]);
        }
        else{
            if(Ne[i].y<=mid) add(Ne[i].z,tot[Ne[i].id]);
            else ans[Ne[i].id]+=getsum(Ne[i].z);
        }
    }
    for(int i=L;i<=R;i++){
        if(Ne[i].y<=mid) lx[++lt]=Ne[i];
        else rx[++rt]=Ne[i];
    }
    for(int i=1;i<=lt;i++) Ne[L+i-1]=lx[i];
    for(int i=1;i<=rt;i++) Ne[L+i+lt-1]=rx[i];
    if(l==r) return;
    cdq(L,L+lt-1,l,mid);
    cdq(L+lt,R,mid+1,r);
}
int main()
{
    n=read();mx=read();
    for(int i=1;i<=n;i++){
        F[i]=(node){read(),read(),read()};
    }
    sort(F+1,F+1+n,cmp);
    F[0]=(node){-2e9,-2e9,-2e9};
    for(int i=1;i<=n;i++){
        if(same(i,i-1)) ++tot[cnt];
        else tot[++cnt]=1,Ne[cnt]=F[i],Ne[cnt].id=cnt;
    }
    cdq(1,cnt,0,mx);
    for(int i=1;i<=cnt;i++) out[ans[i]+tot[i]-1]+=tot[i];
    for(int i=0;i<n;i++) printf("%d\n",out[i]);
    return 0;
}
那我们继续扩展一下,请看下面的题目:BZOJ1935

Description

很久很久以前,在遥远的大陆上有一个美丽的国家。统治着这个美丽国家的国王是一个园艺爱好者,在他的皇家花园里种植着各种奇花异草。有一天国王漫步在花园里,若有所思,他问一个园丁道: “最近我在思索一个问题,如果我们把花坛摆成六个六角形,那么……” “那么本质上它是一个深度优先搜索,陛下”,园丁深深地向国王鞠了一躬。 “嗯……我听说有一种怪物叫九头蛇,它非常贪吃苹果树……” “是的,显然这是一道经典的动态规划题,早在N元4002年我们就已经发现了其中的奥秘了,陛下”。 “该死的,你究竟是什么来头?” “陛下息怒,干我们的这行经常莫名其妙地被问到和OI有关的题目,我也是为了预防万一啊!” 王者的尊严受到了伤害,这是不可容忍的。看来一般的难题是难不倒这位园丁的,国王最后打算用车轮战来消耗他的实力: “年轻人,在我的花园里的每一棵树可以用一个整数坐标来表示,一会儿,我的骑士们会来轮番询问你某一个矩阵内有多少树,如果你不能立即答对,你就准备走人吧!”说完,国王气呼呼地先走了。 这下轮到园丁傻眼了,他没有准备过这样的问题。所幸的是,作为“全国园丁保护联盟”的会长——你,可以成为他的最后一根救命稻草。
Input

第一行有两个整数n,m(0≤n≤500000,1≤m≤500000)。n代表皇家花园的树木的总数,m代表骑士们询问的次数。 文件接下来的n行,每行都有两个整数xi,yi,代表第i棵树的坐标(0≤xi,yi≤10000000)。 文件的最后m行,每行都有四个整数aj,bj,cj,dj,表示第j次询问,其中所问的矩形以(aj,bj)为左下坐标,以(cj,dj)为右上坐标。
Output

共输出m行,每行一个整数,即回答国王以(aj,bj)和(cj,dj)为界的矩形里有多少棵树。
Sample Input

3 1

0 0

0 1

1 0

0 0 1 1

Sample Output

3


这题也可以用CDQ分治来做,我们先考虑查询的点,对于两个点(x,y)和(u,v)组成的矩形,我们可以想象成矩形(1,1 u,v)中的所有点减去矩形(1,1 u,y-1)和矩形(1,1 x-1,v)的点,再加上(1,1 x-1,y-1)里的点(容斥原理yy一下即可知)。然后我们只要求区间(1,1 i,j)中有多少点即可,那我们可以转化成一个三维偏序问题,其中x是时间变量,天然有序。y和z分别是每一个点的横坐标和纵坐标。碰上要插入的点就插入到树状数组里,碰上求值的点就在树状数组里求出来。
当然本题由于树状数组较大,所以每次for来清空会超时,可以进行时间戳优化(具体在程序中)。
#include<bits/stdc++.h>
#define MAXN 500005
#define MAXM 10000005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,cnt,dfn,knum,ans[MAXN],sum[MAXM],tim[MAXM];
struct node{
    int x,y,id,be;
}f[MAXN*5],t[MAXN*5];
int check(node a,node b){
    return a.x==b.x?abs(a.be)<abs(b.be):a.x<b.x;
}
int lowbit(int x){return x&-x;}
void add(int x,int ad){
    while(x<=knum){
        if(tim[x]!=dfn) tim[x]=dfn,sum[x]=0;
        sum[x]+=ad;
        x+=lowbit(x);
    }
}
int getsum(int x){
    int res=0;
    while(x){
        if(tim[x]==dfn) res+=sum[x];
        x-=lowbit(x);
    }
    return res;
}
void cdq(int l,int r){
    if(l==r) return;
    int mid=(l+r)>>1,i=l,j=mid+1,len=l-1;
    cdq(l,mid);
    cdq(mid+1,r);
    dfn++;
    while(i<=mid&&j<=r){
        if(check(f[i],f[j])){
            if(f[i].be==0) add(f[i].y,1);
            t[++len]=f[i++];
        }
        else{
            if(f[j].be) ans[f[j].id]+=f[j].be*getsum(f[j].y);
            t[++len]=f[j++];
        }
    }
    while(i<=mid) t[++len]=f[i++];
    while(j<=r){
        if(f[j].be) ans[f[j].id]+=f[j].be*getsum(f[j].y);
        t[++len]=f[j++];
    }
    for(int i=l;i<=r;i++) f[i]=t[i];
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++){
        int x=read()+1,y=read()+1;
        f[++cnt].x=x;f[cnt].y=y;f[cnt].be=0;
        knum=max(knum,y);
    }
    for(int i=1;i<=m;i++){
        int x=read()+1,y=read()+1,u=read()+1,v=read()+1;
        f[++cnt]=(node){x-1,y-1,i,1};f[++cnt]=(node){x-1,v,i,-1};
        f[++cnt]=(node){u,y-1,i,-1};f[++cnt]=(node){u,v,i,1};
        knum=max(knum,y);knum=max(knum,v);
    }
    cdq(1,cnt);
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}
这题也可以用离线的树状数组来搞,不需要CDQ的一种方法,具体实现大家可以在网上查找,我这里给出我的代码,也讲讲大致的思路。
我们离线建出所有的点,然后进行离散化。我们先离散y,然后按照x的大小来sort一下。这样sort之后就可以用树状数组直接做了。(其实这是一种很常见的思路,当有两维时,直接sort减去一维,然后直接树状数组搞)
#include<bits/stdc++.h>
#define MAXN 500005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,tot,cnt,x[MAXN],y[MAXN],a[MAXN],b[MAXN],c[MAXN],d[MAXN],sum[MAXN*5],p[MAXN*5],ans[MAXN][5];
struct node{
    int x,y,f,id;
}f[MAXN*5];
int lowbit(int x){return x&-x;}
int getsum(int x){
    int res=0;
    while(x){
        res+=sum[x];
        x-=lowbit(x);
    }
    return res;
}
void add(int x,int p){
    while(x<=n){
        sum[x]+=p;
        x+=lowbit(x);
    }
}
int cmp(node a,node b){
    return a.x==b.x?a.f<b.f:a.x<b.x;
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++){
        x[i]=read()+1;y[i]=read()+1;
        p[++tot]=y[i];
    }
    for(int i=1;i<=m;i++){
        a[i]=read()+1;b[i]=read()+1;c[i]=read()+1;d[i]=read()+1;
        p[++tot]=b[i];p[++tot]=d[i];
    }
    sort(p+1,p+1+tot);
    tot=unique(p+1,p+1+tot)-p-1;
    for(int i=1;i<=n;i++){
        y[i]=lower_bound(p+1,p+1+tot,y[i])-p;
        f[++cnt].x=x[i];f[cnt].y=y[i];
    }
    for(int i=1;i<=m;i++){
        b[i]=lower_bound(p+1,p+1+tot,b[i])-p;
        d[i]=lower_bound(p+1,p+1+tot,d[i])-p;
        f[++cnt].x=a[i]-1;f[cnt].y=b[i]-1;f[cnt].id=i;f[cnt].f=1;
        f[++cnt].x=a[i]-1;f[cnt].y=d[i];f[cnt].id=i;f[cnt].f=2;
        f[++cnt].x=c[i];f[cnt].y=b[i]-1;f[cnt].id=i;f[cnt].f=3;
        f[++cnt].x=c[i];f[cnt].y=d[i];f[cnt].id=i;f[cnt].f=4;
    }
    sort(f+1,f+1+cnt,cmp);
    for(int i=1;i<=cnt;i++){
        if(!f[i].f){
            add(f[i].y,1);
        }
        else ans[f[i].id][f[i].f]=getsum(f[i].y);
    }
    for(int i=1;i<=m;i++){
        int x=ans[i][4]-ans[i][3]-ans[i][2]+ans[i][1];
        printf("%d\n",x);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/stevensonson/article/details/80212769
今日推荐