首页 > 其他分享 >CDQ分治

CDQ分治

时间:2024-08-06 12:07:32浏览次数:15  
标签:begin return int auto self 分治 mid CDQ

CDQ分治

CDQ 分治是一种思想而不是具体的算法,与 动态规划 类似。目前这个思想的拓展十分广泛,依原理与写法的不同,大致分为三类:

  • 解决和点对有关的问题。
  • 1D 动态规划的优化与转移。
  • 通过 CDQ 分治,将一些动态问题转化为静态问题。

解决和点对有关的问题

这类问题多数类似于「给定一个长度为 n 的序列,统计有一些特性的点对 (i,j) 的数量/找到一对点 (i,j) 使得一些函数的值最大」。

CDQ 分治解决这类问题的算法流程如下:

  1. 找到这个序列的中点 mid
  2. 将所有点对 (/i/l/?n=24&i=blog/3077605/202408/3077605-20240806120032822-59153689.gif) 划分为 3 类:
    1. 1 \leq i \leq mid,1 \leq j \leq mid 的点对;
    2. 1  \leq i \leq mid ,mid+1 \leq j \leq n 的点对;
    3. mid+1 \leq  i \leq n,mid+1 \leq j \leq n 的点对。
  3. (/i/l/?n=24&i=blog/3077605/202408/3077605-20240806120032822-59153689.gif) 这个序列拆成两个序列 (/i/l/?n=24&i=blog/3077605/202408/3077605-20240806120032822-59153689.gif)(/i/l/?n=24&i=blog/3077605/202408/3077605-20240806120032822-59153689.gif)。此时第一类点对和第三类点对都在这两个序列之中;
  4. 递归地处理这两类点对;
  5. 设法处理第二类点对。

可以看到 CDQ 分治的思想就是不断地把点对通过递归的方式分给左右两个区间。

在实际应用时,我们通常使用一个函数 solve(l,r) 处理 l \leq i \leq r,l \leq j \leq r 的点对。上述算法流程中的递归部分便是通过 solve(l,mid)solve(mid,r) 来实现的。剩下的第二类点对则需要额外设计算法解决。

例题

P3810 【模板】三维偏序(陌上花开) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

template<typename T>
struct BIT {
#ifndef lowbit
#define lowbit(x) (x & (-x));
#endif
    int n;
    vector<T> t;

    BIT () {}
    BIT (int _n): n(_n) { t.resize(_n + 1); }
    BIT (int _n, vector<T>& a): n(_n) {
        t.resize(_n + 1);
        for (int i = 1; i <= n; ++ i) {
            t[i] += a[i];
            int j = i + lowbit(i);
            if (j <= n) t[j] += t[i];
        }
    }
    //单点修改
    void update(int i, T x) {
        while (i <= n) {
            t[i] += x;
            i += lowbit(i);
        }
    }
    //区间查询
    T sum(int i) {
        T ans = 0;
        while (i > 0) {
            ans += t[i];
            i -= lowbit(i);
        }
        return ans;
    }

    T query(int i, int j) {
        return sum(j) - sum(i - 1);
    }
    //区间修改则存入差分数组,[l, r] + k则update(x,k),update(y+1,-k)
    //单点查询则直接求前缀和sum(x)

    //求逆序对
    /*
    iota(d.begin(), d.end(), 0);
    stable_sort(d.begin(), d.end(), [&](int x, int y) {
        return a[x] < a[y];
    });去重排序

    BIT<i64> tree(n);
    i64 ans = 0;
    for (int i = 1; i <= n; i ++) {
        tree.update(d[i], 1);
        ans += i - tree.sum(d[i]);
    }
    */
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    cin >> n >> k;

    vector<array<int, 3>> a(n);
    for (auto &[x, y, z] : a)
        cin >> x >> y >> z;

    sort(a.begin(), a.end());

    vector<array<int, 5>> A;
    for (int i = 0; i < n; i ++) {
        int j = i;
        while (j + 1 < n && a[j + 1] == a[j]) {
            j ++;
        }
        A.push_back({a[j][0], a[j][1], a[j][2], j - i + 1, 0});
        i = j;
    }

    BIT<i64> bit(k);
    auto cdq = [&](auto && self, int l, int r)->void{
        if (l == r)
            return ;
        int mid = l + r >> 1;
        self(self, l, mid);
        self(self, mid + 1, r);

        sort(A.begin() + l, A.begin() + mid + 1, [](auto x, auto y) {
            if (x[1] == y[1]) return x[2] < y[2];
            return x[1] < y[1];
        });

        sort(A.begin() + mid + 1, A.begin() + r + 1, [](auto x, auto y) {
            if (x[1] == y[1]) return x[2] < y[2];
            return x[1] < y[1];
        });

        int i = l, j = mid + 1;
        while (j <= r) {
            while (i <= mid && A[i][1] <= A[j][1]) {
                bit.update(A[i][2], A[i][3]);
                i ++;
            }
            A[j][4] += bit.sum(A[j][2]);
            j ++;
        }

        for (int k = l; k < i; k ++)
            bit.update(A[k][2], -A[k][3]);
    };

    cdq(cdq, 0, A.size() - 1);

    vector<i64> f(n + 1);
    for (auto &[a, b, c, cnt, d] : A) {
        f[d + cnt - 1] += cnt;
    }

    for (int i = 0; i < n; i ++)
        cout << f[i] << "\n";

    return 0;
}

P5094 [USACO04OPEN] MooFest G 加强版 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<array<int, 2>> a(n);
    for (auto &[x, y] : a) {
        cin >> x >> y;
    }

    sort(a.begin(), a.end());

    i64 ans = 0;
    auto cdq = [&](auto && self, int l, int r)->void{
        if (l == r)
            return ;
        int mid = l + r >> 1;
        self(self, l, mid);
        self(self, mid + 1, r);


        sort(a.begin() + l, a.begin() + mid + 1, [](auto x, auto y) {
            if (x[1] != y[1]) return x[1] < y[1];
            return x[0] < y[0];
        });

        sort(a.begin() + mid + 1, a.begin() + r + 1, [](auto x, auto y) {
            if (x[1] != y[1]) return x[1] < y[1];
            return x[0] < y[0];
        });

        int sum1 = 0, sum2 = 0;
        for (int i = l; i <= mid; i ++)
            sum1 += a[i][1];

        int i = l, j = mid + 1;
        while (j <= r) {
            while (i <= mid && a[i][1] < a[j][1]) {
                sum1 -= a[i][1], sum2 += a[i][1];
                i ++;
            }
            int cnt1 = i - l, cnt2 = mid - i + 1;
            ans += 1ll * a[j][0] * (cnt1 * a[j][1] - sum2 + sum1 - cnt2 * a[j][1]);
            j ++;
        }

    };

    cdq(cdq, 0, n - 1);

    cout << ans << '\n';

    return 0;
}

标签:begin,return,int,auto,self,分治,mid,CDQ
From: https://www.cnblogs.com/Kescholar/p/18344891

相关文章

  • 根号分治(待补充)
    最近有根号分治的题都没那么熟悉,想到了方向感也很差,故写一点题解。LCA查询看到题目中的条件\(\sumk\le10^5\)说明\(k\leS\)的个数很多,\(S\lek\)的个数很少。那么对于前者,考虑一开始最朴素的\(O(S^2)\)的暴力,枚举集合中的两个点,求\(LCA\)总时间复杂度\(O(\fra......
  • 「Day 2—贪心问题&分治&前缀和」
    贪心问题定义顾名思义,越贪越好。。。习题P1094[NOIP2007普及组]纪念品分组思路简单来说:最少的+最多的,利用双指针。代码#include<algorithm>#include<iostream>usingnamespacestd;intw,n;intp[30005];intmain(){cin>>w;cin>>n;for(inti=1;i......
  • 边分治维护强连通分量(CF1989F,P5163)
    这里的边分治和树上的点分治边分治不一样,是维护强连通分量用的,每条边有一个出现时间,通过将每条边按连通关系分流重新排列,从而维护每个时间点整张图的连通性。具体的,这个算法是维护这样的一类问题:n个点,m条边按时间顺序依次加入,每加入一条边,你需要回答一些问题,比如在这个时间点......
  • 树分治
    点分治点分治适合处理大规模的树上路径信息问题。LuoguP4178Tree给定一棵有\(n\)个点的带权树,给出\(k\),询问树上距离小于等于\(k\)的点对数量。分治地考虑每一个子树,求出重心,对答案有贡献的是三种点对,依次解决。重心-子树上的点直接累加贡献即可。子树上的点......
  • cdq分治
    cdq分治主要思想为分治,分为三个部分:左区间内部。左区间对右区间。右区间内部。一个保险的标准顺序是先处理左区间,再处理左区间对右区间的贡献,最后处理右区间,这样就可以保证时序性了。注意这种写法在处理左区间对右区间贡献是要先按标号排序分出正确的左右区间,如果是先递归......
  • 解密编程的八大法宝(四)(附二分查找、分治法和图论算法(深度和广度优先搜索、最短路径、最
    算法题中常见的几大解题方法有以下几种::暴力枚举法(BruteForce):这是最基本的解题方法,直接尝试所有可能的组合或排列来找到答案。这种方法适用于问题规模较小的情况,但在大多数情况下效率不高。贪心算法(GreedyAlgorithm):贪心算法在每一步都选择当前看起来最优的解,希望最终能......
  • 点分治板子
    #define_CRT_SECURE_NO_WARNINGS#include<iostream>#include<vector>#include<algorithm>#include<math.h>#include<sstream>#include<string>#include<string.h>#include<iomanip>#include<stdlib.h>#inc......
  • 线段树分治小结
    一般来说,这种题目是给定一些信息,比如说边,然后这些信息会在一个时间段内出现。一般来说需要离线,然后建立一棵以维护每个时刻信息的线段树,具体来说就是在每个节点维护一个vector。#121.「离线可过」动态图连通性以经典例题来说,我们根据每条边出现的时刻将它插入到线段树上对应时......
  • 树分治、动态树分治学习笔记
    点分治点分治适合处理大规模的树上路径信息问题,选取重心,将当前的树拆分为几颗子树,然后递归子树求解问题,但是今天的重点不在这里边分治与点分治类似,选取一条边,均匀地将树分成两个部分,但是对于一个点有多个儿子时,时间复杂度就会非常大,于是我们可以将其转化,这里有两种方法\(1.\)......
  • 归并分治_小和问题
    归并分治原理:1)思考一个问题在大范围上的答案,是否等于,左部分的答案+右部分的答案+跨越左右产生的答案2)计算“跨越左右产生的答案”时,如果加上左、右各自有序这个设定,会不会获得计算的便利性3)如果以上两点都成立,那么该问题很可能被归并分治解决4)求解答案的过程中只需......