分治、CDQ分治小结 A Summary for Divide and Conquer

0. Anouncement

本文部分图片以及部分内容来自互联网,内容过多就不一一注明出处了,冒犯之处还请海涵。 
Some of the pictures and the content of the text come from the Internet. 
Due to plenty of the content, there will be no quotation. If offended, please try to forgive me.

Ⅰ. 分治

  • 分治介绍 
    分而治之,将原问题不断划分为若干个子问题,直到子问题规模足够小可以直接解决 
    子问题间互相独立且与原问题形式相同,递归求解这些子问题,然后将各子问题的解合并得到原问题的解

  • 一般步骤

    • 划分  Divide  
      将原问题划分为若干个子问题,子问题间互相独立且与原问题形式相同
    • 解决  Conquer  
      递归解决子问题(递归是彰显分治优势的工具,仅仅进行一次分治策略也许微不足道, 
      但递归划分到子问题规模足够小,子问题的解可用常数时间解决)
    • 合并  Merge  
      将各子问题的解合并得到原问题的解
  • 时间复杂度

    • 直观估计

      • 分治由以上三部分构成,整体的时间复杂度则由这三部分的时间复杂度之和构成。
      • 由于递归,最终的子问题变得极为简单,以至于其时间复杂度在整个分治策略上的比重微乎其微 
        每次划分的子问题数是多少?每次划分与合并需要的时间是多少?
    • 递归表达式与递归树

      • 递归表达式 
        一般来说,分治将问题划分为 a 个同型子问题,子问题的规模是 n/b ,常数 a≥1, b>1  
        f(n) 是划分与合并 a 个大小为 n/b 的子问题的时间成本 
        f(n) 是一个渐进趋正的函数( ∃n0, 当n>n0, 有f(n)>0 ) 
        递归表达式:T(n)=aT(n/b)+f(n)

      • 递归树 
        递归树是抽象的递归表达式具体化的图形表示,它给出的是一个算法递归执行的成本模型。 
        比如归并排序算法模型以输入规模为 n 开始,一层一层划分,直到输入规模变为 1 为止 

    • 替换法

      替换法是先猜测某个界存在,再用数学归纳法证明正确性,通过解决表达式中的常数 c 来验证答案。 
      举例说明,用替换法证明 T(n)=T(n/4)+T(n/2)+n2 的解为 Θ(n2) ,即分别证明 T(n)=O(n2) 和 T(n)=Ω(n2)  

    • ∗ 主定理

      • 主定理 
        递归表达式 T(n)=aT(n/b)+f(n)  
        T(n) 可能有如下的渐进界: 

      • 主定理的形象解释 

      • T(n)=aT(n/b)+f(n) 的递归树 

        case 1 情况下,递归树的每层成本从根向下呈几何级数增长,成本在叶节点一层达到最高,即最后一次递归是整个过程中成本最高的一次,故其占主导地位。所以递归分治的总成本在渐进趋势上和叶子层的成本一样。 
        case 2 情况下,递归树每层的成本在渐进趋势上一样,即每层都是 nlogba 。由于有 logbn 层,因此总成本为每层的成本乘以 logbn 。 
        case 3 情况下,递归树每层成本呈几何级数递减,树根一层的成本占主导地位。因此,总成本就是树根层的成本。

      • 主定理证明

        • case 1 由所有叶结点的代价决定 

        • case 2 树的代价均匀地分布在各层上 

        • case 3 由根结点的代价决定 

          特别说明: 
          f(n) 代表的是分治中的划分和合并的成本。由于 f(n) 的渐进增长趋势 >Θ(nlogba) , 
          所以该分治的划分和合并的成本高于子问题的解决成本。而如果在这种情况要获得解,划分和合并的成本应该逐级下降 
          否则,划分和合并成本随着划分的推进将呈现发散趋势,这样总成本有可能不会收敛。那么这种分治就显得没有意义了 
          而如果划分与合并成本逐级下降,则意味着函数 f 满足 af(n/b)<=cf(n), c<1 。 
          因此,解为 T(n)=Θ(f(n))


算法课就没去过几节,算导上主定理讲的也不是很详细,其实分析时间复杂度挺好玩的 
转载整理上面的东西主要就是弥补之前书看不懂,课没去听吧,更多的还是结合做题以及具体算法理解吧 
普通分治也不太用讲具体的东西,概念清楚,只要按照步骤来就很简单,并且有现成的例子来结合思考 
两大杀器:快速幂、归并排序。举(照)一(猫)反(画)三(虎)即可

Ⅱ. CDQ分治

  • 与普通分治的区别 
    普通分治中,每一个子问题只解决它本身(可以说是封闭的) 
    CDQ 分治中,对于划分出来的两个子问题,前一个子问题用来解决后一个子问题而不是它本身

  • 适用的情况 
    在很多问题中(比如大多数数据结构题),经常需要处理一些动态问题 
    然而对动态问题的处理总是不如静态问题来的方便,于是就有了 CDQ 分治 
    但使用 CDQ 分治的前提是问题必须具有以下两个性质:

    • 修改操作对询问的贡献独立,修改操作互不影响效果
    • 题目允许使用离线算法。
  • 一般步骤

    • 将整个操作序列分为两个长度相等的部分(分)
    • 递归处理前一部分的子问题(治1)
    • 计算前一部分的子问题中的修改操作对后一部分子问题的影响(治2)
    • 递归处理后一部分子问题(治3)

    特别说明: 
    在整个过程中,最核心的就是步骤3 
    此时前一部分子问题中的修改操作相对后一部分子问题来说是静态处理,因此可以更加方便地计算后一部分子问题

Ⅲ. CDQ分治的题目讲解

怎么学,强撸题吧,持续更新 ing

1. 静态问题

  • 51nod 1376 最长递增子序列的数量 
    入门题の二维偏序,经典题目,点击查看题解    dp+树状数组详解     线段树

  • BZOJ 3262 陌上花开 
    入门题の三维偏序,经典题目,点击查看题解

  • HDU 4742 Pinball Game 3D 
    入门题の三维偏序, 求最长链以及数目,点击查看题解

CDQ 分治:

入门篇:l0nl1f3:简谈CDQ分治

1 概述

CDQ分治是啥? 
会处理操作对于询问的影响吗? 
对于一坨操作和询问,分成两半,单独处理左半边和处理左半边对于右半边的影响,就叫CDQ分治。 
名字是为了纪念金牌选手陈丹琦

2 实现

将左右区间按一定规律排序后分开处理,递归到底时直接计算答案,对于一个区间,按照第二关键字split成两个区间,先处理左区间,之后因为整个区间是有序的,就可以根据左区间来推算右区间的答案,最后递归处理右区间即可。拿此题做比方,先把全区间按照x坐标排序,然后自左向右用前一半(按时间排序)的修改来更新后一半的查询,之后将整个区间按照时间分成两个部分,递归处理。归纳起来就是 
1. 区间分成两半 
2. 计算左区间对右区间的贡献 
3. 撤销修改 
4. 把操作分成两半 
5. 递归下去做

应用链接:http://blog.leanote.com/post/tonyfang/CDQ%E5%88%86%E6%B2%BB


CDQ分治 && HDU 5126

参考文献:从《Cash》谈一类分治算法的应用 — 陈丹琦

也因此简称CDQ分治。

个人理解CDQ分治和普通分治最大的区别为:
普通分治可以将问题拆成几个相互独立的子问题
CDQ分治将问题分成了两个部分:

  1. 相互独立的两个子问题
  2. 两个子问题之间的联系

因此,CDQ分治的算法流程也分为以下几步:

  1. 算法开始
  2. 取一个中间点mid, 将问题分为[L, mid] 和 [mid + 1, R]两个部分。分别处理两个子问题
  3. 处理[L, mid] 和 [mid + 1, R]之间的联系

举个例子:在二维坐标系中,有n个点,m个询问。对于每个询问(x, y),求点(x1, x2) ,x1<= x && y1 <= y 有多少个。

当然,这题用树状数组就直接能写了。但是我们在这里用CDQ分治来解决一下这个问题。

我们将n个点看做是n个更新,并和m个询问放在一起。定义一个结构体用vector存起来。

void sovle1(int l, int r, vector<Struct>vec1) {//在这里将vec1[l, r]按x从小到大排序int mid = (l + r) / 2;solve1(l, mid);solve1(mid + 1, r);//划分成相互独立的子问题//将vec1[l, mid]中的更新操作 和 vec1[mid + 1, r]中的询问操作,存到一个vec2中 solve2(l, r, vec2);//左半边的更新操作和右半边的询问操作之间有联系
}
void solve2(int l, int r, vector<Struct>vec2) {
//此时在solve2中已经不需要管x了。在vec2中所有的更新操作的x必然小于等于询问操作的x。//在这里将vec2[l, r]按y从小到大排序int mid = (l + r) / 2;sovle2(l, mid);solve2(mid + 1, r);//独立子问题//将vec2[l, mid]中的更新操作和vec2[mid + 1, r]中得询问操作存到一个vec3中solve3(l, r, vec3);//
}
void solve3(int l, int r, vector<Struct>vec3) {
//此时在solve3中,x和y都是已经排好序的。从前到后遍历该更新更新该询问询问就好。int cal = 0;for(Struct tmp: vec3) {if(tmp.type == Update)  ++cal;else tmp.query_anser += cal;}
}
//这段代码其中有许多可以优化的地方,但是不要在意细节,主要是为了更加方便地理解算法

虽然解法不如树状数组,但是我们得看到题目扩展后的好处。
我们看到,当到solve2时,函数中的参数vec2已经不用再管他的x了(请务必深刻理解为什么不用再管了)。
这样的好处在哪里?没错,降维!
假如我们要处理的不是二维,而是三维四维依旧可以这么处理,每一个维度只不过加了O(log)的复杂度而已。
如果是随时可以更新查询的操作,只不过也只是把时间当成一个维度而已。(光是这一点就已经牛逼哄哄够解决大部分题目了。)

然后就是下面这道题,三维且可随时更新查询:
HDU 5126 starts: 传送门

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdlib>
using namespace std;
#define FOR(i,l,r)  for(int i=(l); i<=(r); ++i)
#define REP(i,r)    for(int i=0; i<(r); ++i)
#define DWN(i,r,l)  for(int i=(r);i>=(l);--i)
#define pb push_back
typedef long long ll;
typedef pair<int, int>pii;
const int N = 5e4 + 100;
int que[N << 1];
int ans[N];
int z_num;
int node[N << 1];
struct Query {int x, y, z, kind, id;Query() {}Query(int _x, int _y, int _z, int _kind, int _id) : x(_x), y(_y), z(_z), kind(_kind), id(_id) {}
}queries[N << 3], queries_x[N<<3], queries_y[N << 3];
bool cmpx(Query a, Query b) {return a.x < b.x || (a.x == b.x && a.kind < b.kind);
}
bool cmpy(Query a, Query b) {return a.y < b.y || (a.y == b.y && a.kind < b.kind);
}
inline int lowbit(int x)    {return x&-x;}
void update(int pos, int value) {while(pos <= z_num) {node[pos] += value;pos += lowbit(pos);}
}
int query(int pos) {int ret = 0;while(pos) {ret += node[pos];pos -= lowbit(pos);}return ret;
}
void solve3(int l, int r) {//只剩下二维y和z,直接就是二维的更新查询题目,树状数组解决就好了if(l >= r)  return;FOR(i, l, r)if(queries_y[i].kind == 0)  update(queries_y[i].z, 1);else if(queries_y[i].kind == 1)  ans[ queries_y[i].id ] -= query(queries_y[i].z);else if(queries_y[i].kind == 2) ans[ queries_y[i].id ] += query(queries_y[i].z);FOR(i, l, r)if(queries_y[i].kind == 0)  update(queries_y[i].z, -1);
}
void solve2(int l, int r) {//把x这一维度消除if(l >= r)  return;int mid = (l+r)>>1;solve2(l, mid);solve2(mid + 1, r);int num = 0;FOR(i, l, mid)  if(queries_x[i].kind == 0)  queries_y[num++] = queries_x[i];FOR(i, mid+1, r)    if(queries_x[i].kind)  queries_y[num++] = queries_x[i];sort(queries_y, queries_y + num, cmpy);solve3(0, num - 1);
}
void solve1(int l, int r) {//已经默认时间从小到大排序了,把时间这一维度消除if(l >= r)  return;int mid = (l+r)>>1;solve1(l, mid);solve1(mid + 1, r);int num = 0;FOR(i, l, mid)  if(queries[i].kind == 0)    queries_x[num++] = queries[i];FOR(i, mid + 1, r)  if(queries[i].kind)     queries_x[num++] = queries[i];sort(queries_x, queries_x + num, cmpx);//对x从小到大排序solve2(0, num - 1);
}
int main() {int casnum, n;int x1, x2, y1, y2, z1, z2, kind;cin >> casnum;while(casnum--) {memset(ans, -1, sizeof(ans));cin >> n;int id = 0;z_num = 0;REP(i, n) {scanf("%d", &kind);if(kind == 1) {scanf("%d%d%d", &x1, &y1, &z1);queries[id++] = Query(x1, y1, z1, 0, i);que[z_num++] = z1;} else {ans[i] = 0;scanf("%d%d%d%d%d%d", &x1, &y1, &z1, &x2, &y2, &z2);queries[id++] = Query(x2    , y2    , z2    , 2, i);queries[id++] = Query(x2    , y2    , z1 - 1, 1, i);queries[id++] = Query(x2    , y1 - 1, z2    , 1, i);queries[id++] = Query(x1 - 1, y2    , z2    , 1, i);queries[id++] = Query(x2    , y1 - 1, z1 - 1, 2, i);queries[id++] = Query(x1 - 1, y2    , z1 - 1, 2, i);queries[id++] = Query(x1 - 1, y1 - 1, z2    , 2, i);queries[id++] = Query(x1 - 1, y1 - 1, z1 - 1, 1, i);que[z_num++] = z2;que[z_num++] = z1 - 1;}}sort(que, que + z_num);z_num = unique(que, que + z_num) - que;REP(i, id)  queries[i].z = lower_bound(que, que + z_num, queries[i].z) - que + 1;solve1(0, id - 1);REP(i, n)if(ans[i] != -1)printf("%d\n", ans[i]);}return 0;
}



分治、CDQ分治小结相关推荐

  1. 点分治+CDQ分治+整体二分全纪录

    点分治 点分治讲解 解决树上路径问题 经典例题:点分治(长度小于m的路径计数) 经典例题:点分治(聪聪可可) 经典例题:点分治(多个定值路径计数) 经典例题:点分治(采药) 经典例题:点分治+ST表+ ...

  2. 浅谈CDQ分治与偏序问题

    初识CDQ分治 CDQ分治是一个好东西,一直听着dalao们说所以就去学了下. CDQ分治是我们处理各类问题的重要武器.它的优势在于可以顶替复杂的高级数据结构,而且常数比较小:缺点在于必须离线操作. ...

  3. CDQ分治——学习笔记

    前言 因为中国集训队的大佬们总会发明一些新的东西,所以中国的计算机竞赛水平才会一直处于国际前沿,而cdq分治也是某一年的集训队成员发明的算法. 什么是CDQ分治 cdq分治是一种分治(这不是废话嘛), ...

  4. [总结]CDQ分治整体二分

    从昨天到现在除了90%的颓废时间一直在研究一些分治的姿势,主要就是CDQ分治和整体二分. 首先推荐一些学习资料: 陈丹琦 <从 < Cash > 谈一类分治算法的应用> 许昊然 ...

  5. 三维偏序/cdq分治/

    三维偏序---cdq分治 cdq分治概述 二维偏序概述 二维偏序例题分析 三维偏序概述 例题分析 cdq分治概述 前置知识:(如果不懂要先去了解分治) > 分治: > 分而治之,将原问题不 ...

  6. CDQ分治(二维CDQ 、三维CDQ+树状数组、四维CDQ+CDQ+树状数组)

    CDQ分治 CDQ分治相较于普通分治,多了左区间处理后对于右区间的影响. 利用这一点,CDQ分治可以用来做很多数据结构的题目(树状数组.线段树),加一个log的时间复杂度来优化一维. 操作: 假设有两 ...

  7. CDQ分治【分治(真得头疼)

    前言: 说实话我对于这种没有固定板子,变化多端的算法实在是非常头疼的 但是不学不行,这也是一种很重要的思伟方式 所以趁着这几天的心情比较好(快要放大周),赶紧学一波~ 鸣谢:tham,stdcall ...

  8. 分治、CDQ分治小结(need to be updated)

    分治.CDQ分治小结 A Summary for Divide and Conquer 0. Anouncement 本文部分图片以及部分内容来自互联网,内容过多就不一一注明出处了,冒犯之处还请海涵. ...

  9. 【BZOJ-3456】城市规划 CDQ分治 + NTT

    题目链接 http://www.lydsy.com/JudgeOnline/problem.php?id=3456 Solution 这个问题可以考虑dp,利用补集思想 N个点的简单图总数量为$2^{ ...

最新文章

  1. 苹果证实收购Drive.ai自动驾驶汽车初创公司
  2. 【控制】《多智能体系统的协同群集运动控制》陈杰老师-目录
  3. VTK修炼之道11:基本数据结构_数据对象数据集
  4. nextcloud php工程师,教你如何基于宝塔面板和nextcloud搭建自己的网盘
  5. 彻底搞清楚浏览器渲染过程
  6. 读取SSDT表和原函数地址
  7. 关于sizeof的一些东西
  8. 【英语学习】【WOTD】emote 释义/词源/示例
  9. 模板 - 计算几何(合集)
  10. 【纸牌识别】基于matlab形态学扑克牌识别【含Matlab源码 1352期】
  11. python最小特征值_阿里巴巴举荐,Python视频,免费分享,用python求解特征向量和拉普拉斯矩阵...
  12. 【编译原理 思维导图】 陈火旺第三版 前七章
  13. 聊一下《技术力量-一线技术团队成功启示录》
  14. 大数据相关精品资料包分享
  15. 2021年中国原油产量、需求量及石油原油行业发展趋势分析[图]
  16. 金士顿服务器内存条怎么看型号,【查看内存条参数】 内存条上的参数怎么看...
  17. Linux系统下的进程间通信(IPC)
  18. win10+vs2017配置mpi环境的记录(已成功)
  19. 卡罗林斯卡医学院计算机方向,卡罗林斯卡医学院:全球卫生专业受学生欢迎
  20. egg的Cookies,取不到值的问题

热门文章

  1. idea maven打包war包项目
  2. 创新实验室实习生每周工作总结【实习第七周】
  3. 高性能计算服务器8280,AMD秀实力:第二代霄龙64核性能是英特尔Xeon Platinum8280两倍...
  4. 克隆系统安装安全软件避免重复ID 方法
  5. SPI通信协议详解,一篇就够!
  6. 【转】人脸识别图像库
  7. python 实现单链表 Lined List
  8. 电梯门禁系统服务器一般在哪,电梯控制系统与门禁系统有区别吗
  9. SpringBoot 腾讯企业邮箱
  10. 视频理解论文实验笔记2014-2022