浅谈CDQ分治

摘要:
很久以前我想写一篇关于CDQ分裂与征服的博客,但现在我正在填补这个空缺。CDQ分治是一种分治算法,通常用于高维数据结构的降维。例如,通过CDQ分而治之,二维数据结构可以成为一维问题。CDQ分治就是分治运营时间。采用与合并和排序相同的操作,以确保分割和分割时的顺序。此时,这个问题可以通过CDQ divide-and-conquer=rhs.pos){returnpos˃1;CDQ、CDQ;intcnt=0,L=L,R=mid+1,sum=0;而{if{temp[cnt++]=Q[L];//每次查找一个小数组并将其放入临时数组//保持sumif{sum+=Q[L].val;}elseif{sum-=Q[L].val;}L++;}否则{temp[cnt++]=Q[R];如果{ans[Q[R].val]+=sum;}elseif{ans[Q[R].val]-=sum;}R++;}}而{temp[cnt++]=Q[L];如果{sum+=Q[L].val;}elseif{sum-=Q[L].val;}L++;}而{temp[cnt++]=Q[R];如果{ans[Q[R].val]+=sum;}elseif{ans[Q[R].val]-=sum;}R++;}对于{Q[l+i]=temp[i];}}Intmain(){intT;scanf;对于{intcnt=0,Qcnt=0;intn,x,l,r;charop[10];scanf,//如果初始状态不为零,则可以将其视为在{scanf;Q[++cnt]=node;}的开始处加n次而{scanf;if{break;}扫描;如果{Q[++cnt]=节点;}elseif{Q[++cnt]=节点;}否则{//查询操作由前缀Q[++cnt]=node;Q[++-cnt]=node;Qcnt++;}分割}CDQ;脉波重复间隔

很久前就想写篇CDQ分治的blog了,现在填坑。

CDQ分治是一种分治算法,一般用于高维数据结构的降维。比如二维数据结构,可以通过CDQ分治变成一个一维的问题。

CDQ分治本质还是个分治。一般分治操作就是,我想知道一个长度为n的区间产生的贡献有多少,那我可以把区间平均划分成两部分,那么此时问题变成左区间产生的贡献+右区间产生的贡献+左区间对右区间产生的贡献+右区间对左区间产生的贡献。然后递归下去,分别再去算出左区间的贡献和右区间的贡献。假设左区间对右区间产生的贡献+右区间对左区间产生的贡献这部分可以om求出,m为区间长度,那么递归一层的复杂度为on。因为每次都是平均划分下去,所以最多递归logn层。因此复杂度为nlogn。

CDQ分治是对操作时间进行分治。我们知道,一般数据结构的操作都是查询,修改等等。我们认为第i个操作是在第i个时间点进行的。我们回顾下前面的分治,如果我们用普通的分治,很容易想到对时间去分治。也就是说我每次把操作分成两部分,然后考虑左区间对右区间产生的贡献+右区间对左区间产生的贡献如何快速求出来。

举个常见的例子。现在给你n个数字,初始状态为0。现在我有两种操作,一种操作是把某个数的数值加上val,另一个操作是查询区间[l, r]的数字总和。这是很经典的单点更新区间查询问题,可以用线段树或者树状数组去做。现在我们考虑CDQ如何解决这个问题。

首先我对操作时间进行分治,左区间产生的贡献和右区间产生的贡献可以递归求,左区间对右区间产生的贡献+右区间对左区间产生的贡献才是重点。我们发现在这个问题中,只有修改操作会影响到查询。也就是说,只有先出现的修改操作会影响后出现的查询操作。比如说我现在先把坐标为1的点增加1,再查询区间[1, 2]的数字和,再把坐标为2的点增加2。只有第一次修改会对查询有影响。那我可以左区间对右区间产生的贡献+右区间对左区间产生的贡献转化成左区间的修改操作对右区间的查询操作的贡献。总结一下就是,我每次把操作划分成两部分,对于左区间我只考虑修改操作,对于右区间我只考虑查询操作,查询只考虑左区间产生的贡献。然后递归下去。

现在问题是如何查询区间和,我们先利用前缀和思想,把查询sum(l, r)转化成sum(1, r) - sum(1, l - 1)。考虑修改了一个数,会对哪些点的前缀和有影响。显然,如果我修改了第i个点,那么第i个点到第n个点的前缀和都会有影响。换句话说,只有下标小的点会影响下标大的点的查询。那我们可以考虑对操作(修改+查询)按照点的下标排序,下标小的在前面。然后按顺序执行每一步操作,维护一个变量sum,修改操作就对这个sum修改,查询就查询这个sum。(PS:这里有个细节,如果对于同一个点既有查询又有修改,要优先修改)

现在问题是,我们真的需要每次都排序吗?分治+排序,或许我们会联想到某种排序算法——归并排序。归并排序是基于分治的排序,他的思想是要让序列变成有序序列,先把序列分成两部分,先递归处理左右两部分,使得左右两部分是有序的。现在只要考虑如何把两个有序序列合并起来。很简单,因为序列是有序的,所以我们可以每次找出两个序列中最小的值,提取出来放到一个新数组里,并重复k次,k为两个序列的数字总数。这样就可以o(k)得到一个新的有序序列。递归logn层,所以复杂度是nlogn。

显然我们可以利用这种思想把排序部分优化掉。采取和归并排序一样的操作,分治的同时保证序列有序。

到此,这题就可以用CDQ分治解决了。复杂度为nlogn,n为序列长度。

模板题测试:https://vjudge.net/problem/HDU-1166

代码:

浅谈CDQ分治第1张浅谈CDQ分治第2张
#include <bits/stdc++.h>

using namespace std;
const int maxn = 5e4 + 10;
struct node {
    int op, pos, val;
    // op = 1, add; op = 2, del, op = 3, addquery, op = 4, delquery
    // pos 代表操作数的下标
    // val 对于修改操作代表修改的值,对于查询操作代表是第几个查询

    node(int _op = 0, int _pos = 0, int _val = 0) {
        op = _op, pos = _pos, val = _val;
    }

    bool operator < (const node &rhs) const {
        // 优先下标,然后优先查询操作。
        if (pos != rhs.pos) {
            return pos < rhs.pos;
        } else {
            return op < rhs.op;
        }
    }
}Q[maxn * 3], temp[maxn * 3];

int ans[maxn];

void CDQ(int l, int r) {
    if (l >= r) {
        return ;
    }
    int mid = (l + r) >> 1;
    CDQ(l, mid), CDQ(mid + 1, r);
    int cnt = 0, L = l, R = mid + 1, sum = 0;
    while (L <= mid && R <= r) {
        if (Q[L] < Q[R]) {
            temp[cnt++] = Q[L]; //每次找个小的放到临时数组中
            //同时维护sum
            if (Q[L].op == 1) {
                sum += Q[L].val;
            } else if (Q[L].op == 2) {
                sum -= Q[L].val;
            }
            L++;
        } else {
            temp[cnt++] = Q[R];
            if (Q[R].op == 3) {
                ans[Q[R].val] += sum;
            } else if (Q[R].op == 4) {
                ans[Q[R].val] -= sum;
            }
            R++;
        }
    }
    while (L <= mid) {
        temp[cnt++] = Q[L];
        if (Q[L].op == 1) {
            sum += Q[L].val;
        } else if (Q[L].op == 2) {
            sum -= Q[L].val;
        }
        L++;
    }
    while (R <= r) {
        temp[cnt++] = Q[R];
        if (Q[R].op == 3) {
            ans[Q[R].val] += sum;
        } else if (Q[R].op == 4) {
            ans[Q[R].val] -= sum;
        }
        R++;
    }
    for (int i = 0; i < cnt; i++) {
        Q[l + i] = temp[i];
    }
}

int main() {
    int T;
    scanf("%d", &T);
    for (int c = 1; c <= T; c++) {
        int cnt = 0, Qcnt = 0;
        int n, x, l, r;
        char op[10];
        scanf("%d", &n);
        // 如果初始状态不为零,可以当成一开始就进行了n次add操作
        for (int i = 1; i <= n; i++) {
            scanf("%d", &x);
            Q[++cnt] = node(1, i, x);
        }
        while (true) {
            scanf("%s", op);
            if (op[0] == 'E') {
                break;
            }
            scanf("%d%d", &l, &r);
            if (op[0] == 'A') {
                Q[++cnt] = node(1, l, r);
            } else if (op[0] == 'S') {
                Q[++cnt] = node(2, l, r);
            } else if (op[0] == 'Q') {
                //查询操作按照前缀和拆分
                Q[++cnt] = node(3, r, Qcnt);
                Q[++cnt] = node(4, l - 1, Qcnt);
                Qcnt++;
            }
        }
        CDQ(1, cnt);
        printf("Case %d:
", c);
        for (int i = 0; i < Qcnt; i++) {
            printf("%d
", ans[i]);
            ans[i] = 0;
        }
    }
    return 0;
}
View Code

补充:关于前面讲过,CDQ分治可以代替掉一维的数据结构,所以如果需要做二维数据结构,可以对一维进行归并排序,第二维用个树状数组维护。举个例子,比如求矩阵和,可以把矩阵分解成四个前缀矩阵加加减减。然后对其中一维进行归并,但是由于第二维不是有序的,所以要用树状数组维护。

免责声明:文章转载自《浅谈CDQ分治》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇POI写入word docx 07 的两种方法Struts2注解配置之@Namespace(四)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

分治算法(用C++、lua实现)

目录 1、算法 2、C++实现 3、lua实现 本文为 分治算法 的代码实现。 作者水平比较差,有错误的地方请见谅。 1、算法 分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的...

无废话JavaScript(下)

无废话JavaScript(下)上一篇在这里,在这里,在这里……五、函数式这个可不是JavaScript的发明,它的发明人已经死了,而他的这个发明还在困扰着我们……如同爱迪生的灯泡还在照耀着我们。其实函数式语言很简单,它就是一种与命令式语言同样“完备”的语言实现方案。由于它的基础思想与命令式——如果你不想用这个难于理解的名词,那就把它换成C,或者Delph...

oracle start with connect by prior 递归查询用法

start with 子句:遍历起始条件,有个小技巧,如果要查父结点,这里可以用子结点的列,反之亦然。 connect by 子句:连接条件。关键词prior,prior跟父节点列parentid放在一起,就是往父结点方向遍历;prior跟子结点列subid放在一起,则往叶子结点方向遍历,                          parentid...

sql递归

--单表递归 由于项目中经常用到 , 随笔以作下次使用 例如:找ProductType表 下ID为1的分类的所有子级 with result as --result为别名(select * from TB_ProductType where Id=1 --查询ID为1 的数据union allselect TB_ProductType.* from tb...

动态链接库(DLL)

动态链接库和静态链接库: 动态链接库一般不能直接执行,而且它们一般也不接收消息。 它们是包含许多函数的独立文件,这些函数可以被应用程序和其他 DLL 调用以完成某些特定的工作。 一个动态链接库只有在另外一个模块调用其所包含的函数时才被启动。 “静态链接” 一般是在程序开发过程中发生的,用于把一些文件链接在一起创建一个 Windows 可执行文件。 这些文件...

递归可视化之汉诺塔的动画实现(turtle海龟)

import turtle class Stack: def __init__(self): self.items = [] def isEmpty(self): return len(self.items) == 0 def push(self, item): self.items...