总结-CDQ 分治

关于 CDQ 分治

CDQ 分治是一种思想而不是具体的算法,并且必须离线处理,用于维护具有偏序限制的问题。

偏序可以理解为大小关系。

经典三维偏序

CDQ 分治的经典应用。

给定每个元素,每个元素都有三个属性 \((x,y,z)\),要求统计所有满足三个偏序条件时的价值。

标准方法:sort 排序维护第一维偏序,用归并维护第二维偏序,用数据结构(树状数组、线段树)维护第三维偏序。

原理是在处理第二维的时候,使每一个左半区间元素对于每一个右半区间元素一定满足第一维的偏序,同时维护并统计第三维。

给个标准的框架:

归并版本:

归并排序版本的优点是常数小,缺点是必须要后序遍历并且偏序条件所关联的维度相同,要求统计价值时没有严格的先后顺序

经典的有严格的先后顺序的题目如 CDQ 分治套 DP 等,由于转移时要求所有先导状态都已被处理,所以这种题不能用归并版本来写,而需要用中序遍历的 sort 排序版本。

例题:[CQOI2011] 动态逆序对 - 洛谷

#define lowbit(x) ( x&(-x) )
int T[N];

inline void update(int x)
{ for(;x<=n;x+=lowbit(x)) /*维护价值*/; }

inline void Clear(int x)
{ for(;x<=n;x+=lowbit(x)) T[x]=0; }

inline int query(int x)
{
    int sum=0;
    for(;x>0;x-=lowbit(x))
      /*统计价值*/
    return sum;
}

struct ask
{ int x,y,z; }Q[N],tmp[N];
int ans[N];

bool rule_y(ask u,ask v)
{ return u.y<v.y; }


bool rule_x(ask u,ask v)
{ return u.x<v.x; }

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,pos=l;
    for(;j<=r;j++)
    {
        while(i<=mid && Q[i].y<Q[j].y)
        {
            update(Q[i].z);// 树状数组维护第三维
            tmp[pos++]=Q[i++];
        }
        /*
        
        
        */
        // 这里要统计答案
        tmp[pos++]=Q[j];
    }
    for(int k=l;k<i;k++) Clear(Q[k].z);//清空树状数组
    while(i<=mid) tmp[pos++]=Q[i++];
    for(int k=l;k<=r;k++)
      Q[k]=tmp[k];
    /*
    
    
    */
    // 若有需要,这里可能也需要统计答案并清空
}


signed main()
{
	/*

	*/
    // 输入和预处理
    sort(Q+1,Q+n+1,rule_x);
    cdq(1,n);
    // 计算最终答案并输出
	return 0;
}

给出一个不能用归并版本的题目:序列 - 洛谷

sort 版本

后序遍历版本

就是将原来归并排序的过程用 sort 来硬排序,常数大,一般都不这么写。

例题:陌上花开 - 洛谷

#define mid (l+r>>1)
void CDQ(int l,int r)
{
    if(l==r) return ;
    //递归左右 并 按第二维排序
    CDQ(l,mid);     sort(p+l,p+mid+1,rule_y);
    CDQ(mid+1,r);   sort(p+mid+1,p+r+1,rule_y);
    // 按第二维排序后,第一维的顺序虽被打乱
    // 但由于我们是按 第一维 从小到大的顺序 二分
    // 所以对于 左半边的任意x 仍然小于 右半边的任意x
    // 我们计算左半边对右半边贡献的价值时仍满足 第一维的偏序条件

    //归并左右 双指针计算价值
    int i=l,j=mid+1;
    for(;j<=r;j++)
    {
        while(i<=mid && p[i].y<=p[j].y)
        //左半边的i 可以 为右半边的 j 在第二维提供价值
        {
            update(p[i].z,p[i].cnt);
            //将i的第三维计入树状数组
            i++;
        }
        p[j].ans+=query(p[j].z);
        //计算第三维的价值
    }

    //清空树状数组
    for(int k=l;k<i;k++)
      update(p[k].z,-p[k].cnt);
}

中序遍历版本

一个用于处理有严格统计顺序并且偏序条件关联不同维度的题目,比如典型的 CDQ 分治套 DP 题目 序列 - 洛谷

struct node
{
    int p,l,r,x;
}Q[N];

#define lowbit(x) ( x&(-x) )
int T[N];

inline void update(int x,int val)
{
    for(;x<=M;x+=lowbit(x))
      ckmax(T[x],val);
}

inline void Clear(int x)
{
    for(;x<=M;x+=lowbit(x))
      T[x]=0;
}

inline int query(int x)
{
    int ans=0;
    for(;x>0;x-=lowbit(x))
      ckmax(ans,T[x]);
    return ans;
}

bool rule_x(node u,node v)
{ return u.x<v.x; }

bool rule_l(node u,node v)
{ return u.l<v.l; }

bool rule_p(node u,node v)
{ return u.p<v.p; }

void CDQ(int l,int r)
{
    if(l==r) return ;
    int mid=l+r>>1;
    CDQ(l,mid);
    sort(Q+l,Q+mid+1,rule_x);
    sort(Q+mid+1,Q+r+1,rule_l);

    int i=l,j=mid+1;
    while(j<=r)
    {
        while(i<=mid && Q[i].x<=Q[j].l)
        {
            update(Q[i].r,dp[Q[i].p]);
            i++;
        }
        ckmax(dp[Q[j].p],query(Q[j].x)+1);
        j++;
    }
    for(int k=l;k<i;k++) Clear(Q[k].r);
    sort(Q+l,Q+r+1,rule_p);
    CDQ(mid+1,r);
}

四维偏序

大体上就是将用一次排序维护第一维,归并处理第二和第三维,用数据结构处理第四维。

posted @ 2025-09-16 18:37  南北天球  阅读(20)  评论(0)    收藏  举报