CDQ分治

\(CDQ\)分治常用于解决多维偏序关系问题,以三维为例,第一维可以排序解决,第二维进行分治,第三位常用数据结构(树状数组)维护,于是也可以将动态带修改问题,离线处理,常默认时间为第一维。

由于许多时候同一个操作要分成多个操作来解决,所以要格外注意结构体数组的大小是否恰当,同时留意树状数组的边界。

\(CDQ\)优化\(dp\)时,通常是应用于\(1D/1Ddp\)问题,即\(dp\)数组是一维的,转移是一维的,可以从\(O(N^2)\)优化到\(O(Nlog^2N)\),分治时,若\(l=r\)则直接更新\(dp[l]\),否则先递归\((l,mid)\),之后处理所有的\(l<=i<=mid\)对于\(mid+1<=j<=r\)的贡献,再递归\((mid+1,r)\).

P2345 [USACO04OPEN] MooFest G

\(\sum_{i=1}^n\sum_{j=1}^nmax(v[i],v[j])*|x[i]-x[j]|\).

首先按照\(v\)升序排序,考虑左区间对右区间的贡献,设\(i<j\),那么\(max(v[i],v[j])=v[j]\),绝对值分为\(x[i]<x[j]\)\(x[i]>x[j]\),按照\(x\)升序排序,接下来考虑左区间对右区间的共享,找到第一个\(x[i]>x[j]\)的位置,可以得到\(sum=cnt_1*x[j]-\sum_{x[i]<x[j]}x[i]+\sum_{x[i]>x[j]}x[i]-cnt_2*x[j]\),其中\(sum\)为距离和,\(cnt_1\)\(cnt_2\)分别是左区间\(x\)值小于和大于\(x[j]\)的数的数量,即由\(i\)划分开的\(i-l\)\(mid-i+1\).

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,a=0,b=0;
    for(int i=l;i<=mid;i++)a+=q[i].y;
    for(;j<=r;j++){
        for(;i<=mid&&q[i].y<q[j].y;i++){
            a-=q[i].y;
            b+=q[i].y;
        }
        ans+=q[j].x*((i-l)*q[j].y-b+a-(mid-i+1)*q[j].y);
    }
    merge(q+l,q+mid+1,q+mid+1,q+1+r,pre+l,[](node a,node b){return a.y<b.y;});//按照x升序归并排序
    for(int i=l;i<=r;i++)q[i]=pre[i];
}

P5459 [BJOI2016]回转寿司

给定一个序列,求有多少个连续子序列满足其和\(>=L\)\(<=R\).

可会议进行CDQ分治,满足限制l和r的下标连续递增,累加成前缀和\(x\),也就是求满足\(L<=s[j]-s[i]<=R(i<j)\)的点对\((i,j)\)的数量,注意区间长度为一时要判断单点是否合法并更新答案。

void CDQ(int l,int r){
    if(l==r)return ans+=liml<=s[l]&&s[l]<=limr,void();
    int mid=l+r>>1;
    CDQ(l,mid);
    CDQ(mid+1,r);
    int h=l,t=l-1;
    for(int i=mid+1;i<=r;i++){
        while(t+1<=mid&&s[i]-s[t+1]>=liml)t++;/*找到第一个不合法的*/
        while(h<=mid&&s[i]-s[h]>limr)h++;/*找到第一个合法的*/
        ans+=t-h+1;
    }
    sort(s+l,s+r+1);
}

P2717 寒假作业

求一个序列有多少连续子序列满足平均值\(>=k\).

有平均值,则首先将序列所有值减去\(k\),之后判断区间和是否\(>=0\)即可,注意区间长度为一时判断是否满足条件。

void CDQ(int l,int r){
    if(l==r)return ans+=s[l]>=0,void();
    int mid=l+r>>1;
    CDQ(l,mid);
    CDQ(mid+1,r);
    int i=l,j=mid+1,sum=0;
    for(;j<=r;j++){
        for(;i<=mid&&s[j]-s[i]>=0;i++)sum++;
        ans+=sum;
    }
    sort(s+l,s+r+1);
}

P8575 「DTOI-2」星之河

一棵以\(1\)为根的树,有两种点权\(a\)\(b\),问每个点的子树内\(a\)\(b\)权值均小于该点的点的数量。

前两维两种点权偏序,第三维\(dfs\)序偏序,\(dfs\)序较大的优先,这样就可以优先子树。

struct Ask{
    int red,blue,ipt,opt,id;
    inline bool friend operator<(Ask a,Ask b){
        return a.red==b.red?a.blue==b.blue?a.ipt>b.ipt:a.blue<b.blue:a.red<b.red;
    }
}q[N],pre[N];
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(;j<=r;j++){
        for(;i<=mid&&q[i].blue<=q[j].blue;i++)bit.add(q[i].ipt,1);
        ans[q[j].id]+=bit.ask(q[j].ipt,q[j].opt);
    }
    for(--i;i>=l;i--)bit.add(q[i].ipt,-1);
    merge(q+l,q+1+mid,q+1+mid,q+1+r,pre+l,[](Ask a,Ask b){return a.blue==b.blue?a.ipt>b.ipt:a.blue<b.blue;});
    for(int i=l;i<=r;i++)q[i]=pre[i];
}

P3810 【模板】三维偏序(陌上花开)

\(n\)个元素,每个元素有\(a,b,c\)三种属性,\(f(i)\)\(a[j]<=a[i]\&\&b[j]<=b[i]\&\&c[j]<=c[i]\)\(j\)的数量,求\(f(i)=d\)的数量,\(d\in[0,n-1]\).

三维分别是\(x,y,z\),注意需要去重,树状数组的范围是\(z\)的范围,由于CDQ时还会对第二维进行排序,所以需要在结构体内部记录\(ans\),这样排序时才会跟着该改变,而不能用单纯一个数组来记录。

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(;j<=r;j++){
        for(;i<=mid&&q[i].y<=q[j].y;i++)bit.add(q[i].z,q[i].w);
        q[j].ans+=bit.query(q[j].z);
    }
    for(--i;i>=l;i--)bit.add(q[i].z,-q[i].w);
    merge(q+l,q+mid+1,q+mid+1,q+1+r,pre+l,[](Ask a,Ask b){return a.y<b.y;});
    for(int i=l;i<=r;i++)q[i]=pre[i];
}
    for(int i=1;i<=n;i++){
        col++;
        if(q[i].x!=q[i+1].x||q[i].y!=q[i+1].y||q[i].z!=q[i+1].z)q[++m]=q[i],q[m].w=col,col=0;
    }
    CDQ(1,m);
    for(int i=1;i<=m;i++)ans[q[i].ans+q[i].w-1]+=q[i].w;

维护一个支持单点修改和查询矩阵和的矩阵。

对于一个数会对\((1,1)\)\((x,y)\)做出贡献当且仅当横纵坐标和出现时间都小于当前询问,将询问差分,注意所欲坐标要\(+1\)来防止\(0\).

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(;j<=r;j++){
        for(;i<=mid&&a[i].x<=a[j].x;i++)if(a[i].ask==0)bit.add(a[i].y,a[i].w);
        if(a[j].ask==1)a[j].w+=bit.query(a[j].y);
    }
    for(--i;i>=l;i--)if(a[i].ask==0) bit.add(a[i].y,-a[i].w);
    merge(a+l,a+mid+1,a+mid+1,a+r+1,preask+l);
    for(i=l;i<=r;i++)a[i]=preask[i];
}
    while(opt!=3){
        if(opt==1){
            int x,y,v;cin>>x>>y>>v;
            x++,y++;
            a[++cnt]={cnt,x,y,v,0};
        }
        else{
            int x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;x2++,y2++;
            a[++cnt]={cnt,x1,y1,0,1};
            a[++cnt]={cnt,x2,y2,0,1};
            a[++cnt]={cnt,x2,y1,0,1};
            a[++cnt]={cnt,x1,y2,0,1};
        }
        cin>>opt;
    }
    for(int i=1;i<=cnt;i++)if(a[i].ask==1)cout<<a[i].w+a[i+1].w-a[i+2].w-a[i+3].w<<'\n',i+=3;

P4169 [Violet]天使玩偶/SJY摆棋子

单点标记,每次询问到距离询问最近的标记点的距离,距离为曼哈顿距离。

考虑去掉曼哈顿距离的绝对值,设询问\(i\)\(dis(i,j)=|x[i]-x[j]|+|y[i]-y[j]|\),假设\(j\)\(i\)的左下方,当\(x[j]+y[j]\)最大时\(dis\)最小,再满足时间轴要求即为三维偏序,四次CDQ,每次对应一个方向,都等效成查找距离当前点最近的左下方的点,\(maxx-x\)相当于左右翻转(查找右下),\(maxy-y\)相当于上下翻转(查找左上),两者同时做即查找右上,查询\(|x_1-x_2|+|y_1-y_2|\),翻转后等效成查询\((x_1+y_1)-(x_2+y_2)\),于是可以将\(x+y\)作为整体用树状数组维护最大值。

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(;j<=r;j++)
    if(!a[j].ask){
        for(;a[i].x<=a[j].x&&i<=mid;i++)
        if(a[i].ask)bit.add(a[i].y,a[i].x+a[i].y);
        int t=bit.query(a[j].y);
        if(t)ans[a[j].tim]=min(ans[a[j].tim],a[j].x+a[j].y-t);
    }
    for(j=l;j<i;j++)if(a[j].ask)bit.clear(a[j].y);
    merge(a+l,a+mid+1,a+mid+1,a+r+1,q+l);
    for(i=l;i<=r;i++)a[i]=q[i];
}
inline void del(){
    rx=ry=m=0;
    for(int i=1;i<=n;i++)if(!a[i].ask)rx=max(rx,a[i].x),ry=max(ry,a[i].y);
    for(int i=1;i<=n;i++)if(a[i].x<=rx&&a[i].y<=ry)q[++m]=a[i];
    for(int i=1;i<=m;i++)a[i]=q[i];
}
    for(int i=1;i<=n;i++)p[i]=a[i];
    del();CDQ(1,m);
    for(int i=1;i<=n;i++)a[i]=p[i],a[i].x=lx-a[i].x;
    del();CDQ(1,m);
    for(int i=1;i<=n;i++)a[i]=p[i],a[i].y=ly-a[i].y;
    del();CDQ(1,m);
    for(int i=1;i<=n;i++)a[i]=p[i],a[i].x=lx-a[i].x,a[i].y=ly-a[i].y;
    del();CDQ(1,m);

对一个排列,按照某种顺序依次删除\(m\)个元素,求每次删除之前的逆序对数。

分成两部分考虑,\(tim[i]<tim[j]\&\&pos[i]<pos[j]\&\&v[i]>v[j]\)\(tim[i]<tim[j]\&\&pos[i]>pos[j]\&\&v[i]<v[j]\),之后可以进行分治,再前缀和累加。

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(;j<=r;j++){
        for(;a[i].x<=a[j].x&&i<=mid;i++)bit.add(a[i].y,a[i].flag);
        ans[a[j].id]+=a[j].flag*bit.ask(a[j].y+1,n);
    }
    for(j=l;j<i;j++)bit.add(a[j].y,-a[j].flag);
    i=mid,j=r;
    for(;j>mid;j--){
        for(;a[i].x>=a[j].x&&i>=l;i--)bit.add(a[i].y,a[i].flag);
        ans[a[j].id]+=a[j].flag*bit.query(a[j].y-1);
    }
    for(j=i+1;j<=mid;j++)bit.add(a[j].y,-a[j].flag);
    merge(a+l,a+mid+1,a+mid+1,a+r+1,preask+l,cmp0);
    for(i=l;i<=r;i++)a[i]=preask[i];
}
    for(int i=1;i<=n;i++)cin>>b[i],pos[b[i]]=i,a[++cnt]={cnt,i,b[i],1,1,0};
    for(int i=1,x;i<=m;i++)cin>>x,a[++cnt]={cnt,pos[x],x,-1,-1,i};
    for(int i=1;i<=cnt;i++)preask[i]=a[i];
    CDQ(1,cnt);
    for(int i=1;i<=m;i++)ans[i]+=ans[i-1];
    for(int i=0;i<m;i++)cout<<ans[i]<<'\n';

一个序列,有的数可能会发生变化,但是同一时刻最多只有一个数发生变化,求一个子序列使得任意变化都是不降的。

\(CDQ\)分治优化\(dp\),对于\(x<y\),当且仅当\(val[x]<=mi[y]\&\&ma[x]<=val[y]\)时可以满足题目要求(修改没有继承),\(CDQ\)时先将左区间按最大值升序排序,再将右区间按\(val\)升序排序,之后维护,最后再复原打乱的原数组(按\(pos\)升序排序)。

void CDQ(int l,int r){
    if(l==r)return f[l]=max(f[l],1),void();
    int mid=l+r>>1;
    CDQ(l,mid);
    sort(a+l,a+mid+1,[](node a,node b){return a.ma<b.ma;});
    sort(a+mid+1,a+r+1,[](node a,node b){return a.val<b.val;});
    int i=l,j=mid+1;
    for(;j<=r;j++){
        for(;a[i].ma<=a[j].val&&i<=mid;i++)bit.add(a[i].val,f[a[i].pos]);
        f[a[j].pos]=max(f[a[j].pos],bit.ask(a[j].mi)+1);
    }
    while(i>l)bit.clear(a[--i].val);
    sort(a+mid+1,a+r+1,[](node a,node b){return a.pos<b.pos;});
    CDQ(mid+1,r);
}
posted @ 2022-11-14 17:52  半步蒟蒻  阅读(101)  评论(0)    收藏  举报