CDQ 从二维偏序到三维偏序 从逆序对到动态逆序对 详解

本文详细介绍了CDQ分治思想及其在二维偏序(逆序对)和三维偏序(动态逆序对)问题中的应用。通过排序与分治的结合,解决了二维偏序问题,并探讨了如何扩展到三维偏序,强调了在三维偏序中时间维度的重要性。同时,文章提供了例题和动态逆序对问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CDQ 从二维偏序到三维偏序 从逆序对到动态逆序对详解

CDQ 分治思想

CDQ分治,顾名思义,这个思想重点在于如何
分:将问题从整体分解成几个小部分(一般是两个)。
治:考虑各部分之间的贡献并计算,把部分合并到整体。

二维偏序 (逆序对)

二维偏序模板问题:
一个平面上,给出n个点(X,Y)。
定义: 设 A,B 两点,如果A.x<B.x && A.y<B.y 则A点对B点产生贡献。
求每个点的权值。

最方便的解决办法当然是树状数组,对其中一维(x)排序,另一维(y)在以x的顺序插入树状数组中时,查询答案即可。
这里不累述。

还有一种方法就是CDQ分治:
附上自己写的伪代码:

void CDQ(int l,int r)
{
   
   
    if(r-l<1) return; // 结束条件
    
    /*
    分 
    预处理排序x;
    此时二分处理时左右区间左区间x全部小于右区间的x
    所以右区间不会对左区间产生贡献
    */
    int m = (l+r)>>1;  // 二分处理
    CDQ(l,m);  //  递归处理左边界
    CDQ(m+1,r);  //  递归处理右边界
    
    /*
    治
    合并区间时,由于递归处理,此时左右两部分的y是有序的  见下
    而且在分的时候x是有序的
    所以只有左区间会对右区间产生贡献
    我们便可以在线性时间内处理左区间对右区间的贡献并以y排好序
    */
    int p = l , q = m+1 , t = 0;
    while(q<=r || p<=m){
   
   
        /*
        指针移动法
        处理左区间对右区间的贡献;
        并把排好序的结构体数组付给中间数组temp
        */
    }
    t = 0;
    for(int i=l;i<=r;i++)   // 整体排序 所以说上面合并时已经有序
        z[i] = temp[++t];
}

给出例题 : HDU 1541

三维偏序 (动态逆序对)

在刚刚二维偏序的基础上我们给定n个点加入平面的顺序,每个点只考虑当前时间的点对它产生贡献,不考虑后加的点。
这时,应该扩充平面的点一个时间元素t,设定第一次加入点时t=1,往后时间t增加,一直到n。
这时A对B产生贡献,不仅需要A.x<B.x && A.y<B.y,还需要A.t<B.t,即B点存在时A点必定存在。
这便是一个三维偏序问题

那么我们如何处理三维偏序
我们已经知道 分治+排序 与 树状数组+排序 可以解决二维偏序问题, 排序一维+分治一维 = 二维排序一维 + 树状数组一维 = 二维那我们能否 排序一维 + CDQ一维 + 树状数组一维 = 三维 ?
不可以
答案当然是可以

我大致讲一下: 首先排序使x有序,然后进行CDQ分治,其基本还是和二维偏序时操作一样,但是在整体合并的处理上,不是直接查找,而是以y的顺序插入到树状数组当中,然后以t查找。

这是大致思维
更高维的偏序问题我未接触过,但是应该和我上述一样,升维即可,树套树,CDQ套CDQ等等…

动态逆序对 HYSBZ - 3295 包含 AC代码在这里插入图片描述

#include<bits/stdc++.h>
### 关于CDQ分治的基础练习题目 #### 三维偏序问题入门示例 考虑一个经典的三维偏序问题,给定n个三元组(a_i,b_i,c_i),求对于每一个i(1≤ i ≤ n),有多少个j满足a_j ≤ a_i, b_j ≤ b_i 和 c_j ≤ c_i (j ≠ i)[^1]。 这个问题可以通过CDQ分治来高效解决。核心思路在于将原问题分解成更小规模的子问题,并通过处理这些子问题之间的关系得到最终解。具体来说,在每次划分过程中,先按照某一维度排序(比如a),再利用已排序的结果递归地解决问题的一半对另一半的影响。 ```cpp #include <algorithm> using namespace std; struct node { int x,y,z,id; }p[200005],tmp[200005]; int bit[200005],ans[200005]; bool cmp_x(const node& a,const node& b){ return a.x<b.x||(a.x==b.x&&a.y<b.y)||(a.x==b.x&&a.y==b.y&&a.z<b.z); } void add(int pos,int val){ while(pos<=200000){ bit[pos]+=val; pos+=pos&(-pos); } } int query(int pos){ int ret=0; while(pos>0){ ret+=bit[pos]; pos-=pos&(-pos); } return ret; } void cdq(int l,int r){ if(l>=r)return ; int mid=(l+r)>>1,i=l,j=mid+1,k=l; cdq(l,mid);cdq(mid+1,r); sort(p+l,p+mid+1,cmp_y); sort(p+mid+1,p+r+1,cmp_y); for(;k<=r;k++){ tmp[k]=p[j]; j++; }else{ ans[p[i].id]+=query(p[i].z); add(p[i].z,1); tmp[k++]=p[i++]; } for(i=l;i<j;i++)if(tmp[i].id<=mid)add(tmp[i].z,-1); copy(tmp+l,tmp+r+1,p+l); } ``` 上述代码片段展示了如何应用CDQ分治方法去计算三维偏序中的逆序对数量。这里`cdq()`函数实现了主要逻辑,它不仅解决了当前区间内的部分问题,还负责统计左区间的元素对右区间产生的影响。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值