当前位置: 首页 > news >正文

CDQ分治

例题:
P7883 平面最近点对(加强加强版) - 洛谷
P5094 MooFest G 加强版 - 洛谷
P3810 【模板】三维偏序(陌上花开) - 洛谷

核心思想:

CDQ分治是一种将高维问题不断降维处理的离线分治算法,主要用于计算“一侧”
对“另一侧”的贡献;
可用于处理多维偏序问题、不支持在线查询但允许离线处理的动态问题;


分治步骤:

不妨假设为三维偏序问题X、Y、Z

  • Step 1:降维-排序
    • 首先对第一维X进行排序,若X相同则按Y排序,以此类推;
    • 这确保了在下一步的分治过程中左侧的第一维一定小于右侧;
  • Step 2:分治
    • 定义递归函数cdq( l, r );
    • 递归跳出条件l >= r
    • 中点mid = l + ((r - l) >> 1);
    • 递归左半部分cdq(l, mid)
    • 递归右半部分cdq(mid+1, r)
  • Step 3:计算左侧对右侧的贡献、合并两侧
    • 二维问题,可以使用双指针数据结构(如树状数组或线段树)解决
    • [l, mid][mid + 1, r] 两个子区间内的点按第二维 X 排序
    • 使用两个指针,i 遍历右区间的点,j 遍历左区间的点。
    • 对于每个右区间的点 \(P_i\),我们将所有满足条件的左区间点 \(P_j\) 的第三维 \(P_j.z\) 加入到一个以 z 为下标的树状数组中。
    • 然后,查询树状数组中下标小于等于 \(P_i.z\) 的值的总和,这个总和就是左区间对 \(P_i\) 的贡献。将这个贡献累加到 \(P_i\) 的答案中。

实现细节与注意事项

  • 去重: 如果存在完全相同的点 \((x,y,z)\),在排序和计算时需要特别处理,否则可能导致重复计算。一个常见的技巧是先对所有点去重,并记录每个点的出现次数。
  • 归并排序的妙用: 在计算贡献的步骤中,如果使用归并排序来对 b 维进行排序,可以省去每次都调用 sort 的开销,将这一步的复杂度从 \(O(nlogn)\) 优化到 \(O(n)\),从而使整体算法的结构更清晰。
  • 数据结构的选择: 树状数组因其代码简短、常数小,是处理第三维时的首选。
  • 离散化: 如果 y 或 z 的值域很大,需要先进行离散化。

题解:

  1. P3810 【模板】三维偏序(陌上花开) - 洛谷
    思路与分治步骤相同,时间复杂度\(O(nlog^2n)\)
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    #define IOS ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)// 快读
    template <typename T>
    inline T read() {T x = 0; bool neg = false; char ch = getchar();while (!isdigit(ch)) { if (ch == '-') neg = true; ch = getchar(); }while (isdigit(ch)) { x = x * 10 + (ch ^ 48); ch = getchar(); }return neg ? -x : x;
    }// 快写
    template <typename T>
    inline void write(T x) {if (x < 0) { putchar('-'); x = -x; }if (x > 9) write(x / 10);putchar(x % 10 + '0');
    }struct node
    {int x, y, z, v, ans;node (int x, int y, int z){this->x = x;this->y = y;this->z = z;this->v = 1;this->ans = 0;}node (){}
    };int MAXN = 1e5+5;
    int MAXK = 2e5+5;
    vector<node> a(MAXN);
    vector<node> b(MAXN);
    vector<int> sz(MAXK, 0);int low_bit(int x){return x & -x;
    }void add(int x, int v){while(x < MAXK){sz[x] += v;x += low_bit(x);}
    }int querry(int x){int rst = 0;while(x > 0){rst += sz[x];x -= low_bit(x);}return rst;
    }int cmp(node a, node b){if(a.x == b.x){if(a.y == b.y){return a.z < b.z;}return a.y < b.y;}return a.x < b.x;
    }void cdq(int l, int r){if(l >= r) return;int mid = l + ((r-l) >> 1);cdq(l, mid);cdq(mid+1, r);//计算左部分对右部分的贡献int i = l;for(int j = mid+1; j <= r; j++){while(i <= mid && a[i].y <= a[j].y){add(a[i].z, a[i].v);i++;}a[j].ans += querry(a[j].z);}//树状数组回滚还原for(int k = l; k < i; k++){add(a[k].z, -a[k].v);    }//按y排序 为下一次cdq做准备int pos = l, rr = mid+1, ll = l;while(rr <= r && ll <= mid){if(a[ll].y <= a[rr].y){b[pos++] = a[ll++];}else {b[pos++] = a[rr++];}}while(rr <= r){b[pos++] = a[rr++];}while(ll <= mid){b[pos++] = a[ll++];}for(int k = l; k <= r; k++){a[k] = b[k];}
    }signed main(){IOS;int n, k;n = read<int>(); k = read<int>();for(int i = 0; i < n; i++){int x, y, z;x = read<int>(); y = read<int>(); z = read<int>();a[i] = node(x, y, z);}sort(a.begin(), a.begin()+n, cmp);//去重计数int j = 0;for(int i = 1; i < n; i++){if(a[i].x == a[j].x && a[i].y == a[j].y && a[i].z == a[j].z){a[j].v++;}else {a[++j] = a[i];}}cdq(0, j);vector<int> ans(MAXN, 0);for(int i = 0; i <= j; i++){ans[a[i].ans + a[i].v - 1] += a[i].v;}for(int i = 0; i < n; i++){write(ans[i]);putchar('\n');}
    }
    
  2. P5094 MooFest G 加强版 - 洛谷
    思路:
    对题意进行转化, 存在n个点\((x, v)\), 求

    \[\]

    $$
    
    对于点对\((i, j)\)\((j, i)\)而言二者相同, 因此我们可以先对\(v\)进行排序
    不妨假设排序后\(v_i > v_j (i > j)\) ,此时的求和公式变成了

    \[ \sum_{i=1}^nv_i×\sum_{j=1}^{i-1}abs(x_i - x_j) \]

    我们发现此时一个点对答案所做的贡献只受影响于其左侧的点, 故我们可以进行CDQ分治
    在每次递归中统计左部分$x$的前缀和,使用双指针,若左指针$x$小于右指针$x$则从前缀和中减去左指针的部分, 该部分的$x$之差为正, 当左指针的$x$不再小于右指针时, 左侧区间的剩余部分均大于右指针, 该部分的$x$之差为负数, 如此便可算出左侧区间对当前右指针的贡献
    
      int ll = l;for(int i = mid+1; i <= r; i++){while(a[ll].x < a[i].x && ll <= mid){ss += a[ll].x;sl -= a[ll].x;ll++;}ans += (a[i].y * (((ll-l) * a[i].x - ss) + (sl - (mid-ll+1) * a[i].x)));}
    
    按照\(x\)大小对左右部分进行归并, 维持\(x\)的单调性
    时间复杂度\(O(nlogn)\)
    代码如下:
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    #define IOS ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)int MAXN = 5e4+10, n, ans = 0;struct node
    {int x, y;node (int x, int y){this->x = x;this->y = y;}node (){}
    };vector<node> a(MAXN);
    vector<node> temp(MAXN);int cmp(node a, node b){return a.y < b.y;
    }void cdq(int l, int r){if(l >= r) return ;int mid = (l + r) >> 1;cdq(l, mid);cdq(mid+1, r);int sl = 0, ss = 0;for(int i = l; i <= mid; i++){sl += a[i].x;}int ll = l;for(int i = mid+1; i <= r; i++){while(a[ll].x < a[i].x && ll <= mid){ss += a[ll].x;sl -= a[ll].x;ll++;}ans += (a[i].y * (((ll-l) * a[i].x - ss) + (sl - (mid-ll+1) * a[i].x)));}int i = l, j = mid+1, cnt = l;while(i <= mid && j <= r){if(a[i].x <= a[j].x){temp[cnt++] = a[i++];}else {temp[cnt++] = a[j++];}}while(i <= mid){temp[cnt++] = a[i++];}while(j <= r){temp[cnt++] = a[j++];}for(int i = l; i <= r; i++){a[i] = temp[i];}
    }signed main(){IOS;cin >> n;for(int i = 0; i < n; i++){node t;cin >> t.y >> t.x;a[i] = t;}sort(a.begin(), a.begin()+n, cmp);cdq(0, n-1);cout << ans;
    }
  3. P7883 平面最近点对(加强加强版) - 洛谷
    思路:
    $$
    min_{(i \neq j)}({\sqrt{(x_i-x_j)^2 + (y_i - y_j)^2}})
    $$
    当所有点依照\(x\)单调排列时我们可以将整个区间分成两部分,最小距离的点对无非三种情况
    1. 全部位于左半部分
    2. 全部位于右半部分
    3. 一左一右
    由此我们可以先分别求出左右部分的最短距离\(midis\),即完成了第一和第二种情况的讨论那么对于第三种情况我们只需要从中点分别向左右部分延伸\(midis\)的距离作为中间部分,即得到了所有可能成为最小值的跨越两个部分的点对的情况,再计算最小值;故我们先对所有点按照\(x\)排序;

    当我们从上一步递归得到左右区间的最小值后按照\(y\)的大小进行归并排序,再遍历整个区间, 将处在中间区间的点加入一个数组中,在该数组中再次计算最小距离;此处判断\(x\)合法的每个点对的\((y_i - y_j)\)是否合法, 由于此时\(y\)单调 若此\(y_j\)不合法则对于当前的\(y_i\)后续的所有\(y_j\)均不合法,通过剪枝大大提高了效率;

    最后返回最小值即为区间\([l, r]\)中的最小点对距离
    时间复杂度\(O(nlogn)\)
    代码如下:
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    #define IOS ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)int MAXN = 5e4+10, n, ans = 0;struct node
    {int x, y;node (int x, int y){this->x = x;this->y = y;}node (){}
    };vector<node> a(MAXN);
    vector<node> temp(MAXN);int cmp(node a, node b){return a.y < b.y;
    }void cdq(int l, int r){if(l >= r) return ;int mid = (l + r) >> 1;cdq(l, mid);cdq(mid+1, r);int sl = 0, ss = 0;for(int i = l; i <= mid; i++){sl += a[i].x;}int ll = l;for(int i = mid+1; i <= r; i++){while(a[ll].x < a[i].x && ll <= mid){ss += a[ll].x;sl -= a[ll].x;ll++;}ans += (a[i].y * (((ll-l) * a[i].x - ss) + (sl - (mid-ll+1) * a[i].x)));}int i = l, j = mid+1, cnt = l;while(i <= mid && j <= r){if(a[i].x <= a[j].x){temp[cnt++] = a[i++];}else {temp[cnt++] = a[j++];}}while(i <= mid){temp[cnt++] = a[i++];}while(j <= r){temp[cnt++] = a[j++];}for(int i = l; i <= r; i++){a[i] = temp[i];}
    }signed main(){IOS;cin >> n;for(int i = 0; i < n; i++){node t;cin >> t.y >> t.x;a[i] = t;}sort(a.begin(), a.begin()+n, cmp);cdq(0, n-1);cout << ans;
    }
http://www.sczhlp.com/news/49178/

相关文章:

  • 《valgrind 交叉编译安装》
  • python中的 GIL全局解释器锁
  • Java-注解集合(javax.validation.constraints)
  • 做ppt素材的网站有哪些网站搭建的流程
  • 网站后台管理系统cmssem是什么公司
  • 如何设计自己的网站打开百度一下的网址
  • 厦门广告公司有哪些谷歌seo实战教程
  • wordpress site南昌seo代理商
  • 有初中生做的网站吗网络营销就是seo正确吗
  • 做音乐网站多少钱seo优化排名教程百度技术
  • 衣服定制seo哪家强
  • 教育门户网站建站在seo优化中
  • 企业网站一般要素网站如何被百度快速收录
  • 网站绝对路径301上海seo推广整站
  • 网站后台改网页底色网络营销项目策划书
  • Chapter one.
  • 基于Si4463的实现跳频收发、数据包大小64字节、空中数据速率300kbps的代码
  • 【2025】WinRAR免费版解压缩软件下载安装教程(个人免费版去广告)
  • 近期理工类学术会议推荐|机械制造、土木工程、人工智能、电子材料、机器人等EI会议合集
  • python上下文管理器
  • 看wordpress导出文章企业seo案例
  • 上海公司网站建设搜索关键词分析
  • 辽宁建设工程信息网官方网站营口seo
  • 南平网站建设网站seo优化皆宣徐州百都网络不错
  • 读大语言模型10人工智能进化
  • el-tree-select回显id,不显示label的问题
  • 美德乐流水线常规保养
  • 商会网站建设方案书千锋培训机构官网
  • 高端web开发win优化大师
  • 一站式做网站技术品牌营销策略