CDQ分治小结

CDQ分治小结

warning:此文仅用博主复习使用,初学者看的话后果自负。。

复习的时候才发现以前根本就没写过这种东西的总结,简单的扯一扯

cdq分治的经典应用就是解决偏序问题

比如最经典的三维偏序问题

给出\(n\)个数,每个数\(i\),有三个属性\(a_i, b_i, c_i\),现在我们要统计对于每个\(i\),\(a_j \leqslant a_i, b_j \leqslant b_i, c_j \leqslant c_i\)的个数

显然我们可以先把所有数都按\(a_i\)排序一遍,这样考虑每个位置\(i\)的时候只需要考虑它前面的贡献即可

接下来我们递归处理区间\([1, N]\)。

设分治中心为\(mid\),cdq分治的主要思想递归处理每一段区间,只考虑过分治中心的贡献。

同时,我们采用归并排序的思想,保证每一次统计答案的时候区间\([l, mid]\)和\([mid +1, r]\)内的元素的\(b_i\)都是相对有序的

这样我们只需要用两个指针扫一遍,同时用树状数组来维护一下\(c_i\)即可

好像说的挺抽象的,貌似直接看代码会好很多?

void CDQ(int l, int r) {
    if(l >= r) return ;//区间不合法
    int mid = l + r >> 1;
    CDQ(l, mid); CDQ(mid + 1, r);//递归下去处理子区间,处理完之后保证区间内的bi相对有序
    int nl = l, nr = mid + 1, top = l - 1, sum = 0;//使用两个指针来归并本区间
    while(nl <= mid || nr <= r) {//st数组记录的时把两端区间按bi大小合并后的值
        if((nr > r) || (nl <= mid && A[nl].b <= A[nr].b)) T.add(A[nl].c, A[nl].w), st[++top] = A[nl++];//用树状数组维护ci的贡献
        else A[nr].id += T.Query(A[nr].c ), st[++top] = A[nr++];//直接查询即可
    }
    for(int i = l; i <= mid; i++) T.add(A[i].c, -A[i].w);//把左边区间的影响消除
    for(int i = l; i <= r; i++) A[i] = st[i];//按bi排序
}

然而有一种非常恶心的情况:即\(a_i = a_j, b_i = b_j, c_i = c_j\)

他们内部的贡献往往是不好考虑的,一个最直观的想法是直接把这些相同的数看成一个,统计答案的时候直接加上他们的数量即可

模板

洛谷P3810 【模板】三维偏序(陌上花开)

#include<bits/stdc++.h>
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<22, stdin), p1 == p2) ? EOF : *p1++)
using namespace std;
const int MAXN = 2e5 + 10;
char buf[(1 << 22)], *p1 = buf, *p2 = buf;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
char obuf[1<<24], *O=obuf;
void print(int x) {
    if(x > 9) print(x / 10);
    *O++= x % 10 + '0';
}
int N, ans[MAXN];
struct Array {
    int a, b, c, id, w;
    bool operator == (const Array &rhs) const {
        return a == rhs.a && b == rhs.b && c == rhs.c;
    }
    bool operator < (const Array &rhs) const {
        return(a == rhs.a ? (b == rhs.b ? c < rhs.c : b < rhs.b) : a < rhs.a);
    }
}A[MAXN], st[MAXN];
struct Node {
#define lb(x) (x & (-x))
    int T[MAXN], Lim;
    void add(int x, int v) {
        while(x <= Lim) T[x] += v, x += lb(x);
    }
    int Query(int x) {
        int ans = 0;
        while(x) ans += T[x], x -= lb(x);
        return ans;
    }
}T;
void CDQ(int l, int r) {
    if(l >= r) return ;
    int mid = l + r >> 1;
    CDQ(l, mid); CDQ(mid + 1, r);
    int nl = l, nr = mid + 1, top = l - 1, sum = 0;
    while(nl <= mid || nr <= r) {
        if((nr > r) || (nl <= mid && A[nl].b <= A[nr].b)) T.add(A[nl].c, A[nl].w), st[++top] = A[nl++];
        else A[nr].id += T.Query(A[nr].c ), st[++top] = A[nr++];
    }
    for(int i = l; i <= mid; i++) T.add(A[i].c, -A[i].w);
    for(int i = l; i <= r; i++) A[i] = st[i];
}
int main() {
    N = read(); T.Lim = read();
    for(int i = 1; i <= N; i++) A[i].a = read(), A[i].b = read(), A[i].c = read(), A[i].w = 1;
    stable_sort(A + 1, A + N + 1);
    int num = 1;
    for(int i = 2; i <= N; i++){
        if(A[i] == A[num]) A[num].w++;
        else A[++num] = A[i];
    }
    CDQ(1, num);
    for(int i = 1; i <= num; i++) ans[A[i].id + A[i].w - 1] += A[i].w;
    for(int i = 0; i < N; i++) print(ans[i]), *O++ = '\n';
    fwrite(obuf, O-obuf, 1 , stdout);
    return 0;
}

原文地址:https://www.cnblogs.com/zwfymqz/p/10111650.html

时间: 2024-10-11 11:43:20

CDQ分治小结的相关文章

CDQ分治与整体二分小结

前言 这是一波强行总结. 下面是一波瞎比比. 这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊. 很多很显然的东西都看不出来 分治分不出来 打不出来 调不对 上午下午晚上的效率完全不一样啊. 完蛋.jpg 绝望.jpg. 关于CDQ分治 CDQ分治,求的是三维偏序问题都知道的. 求法呢,就是在分治外面先把一维变成有序 然后分治下去,左边(l,mid)关于右边(mid+1,r)就不存在某一维的逆序了,所以只有两维偏序了. 这个时候来一波"树状数组求逆序对"的操作搞一下二维偏序 就可

CDQ分治题目小结

CDQ分治属于比较特殊的一类分治,许多问题转化为这类分治的时候,时空方面都会有很大节省,而且写起来没有这么麻烦. 这类分治的特殊性在于分治的左右两部分的合并,作用两部分在合并的时候作用是不同的,比如,通过左半部分的影响来更新右半部分,所以分治开始前都要按照某一个关键字排序,然后利用这个顺序,考虑一个区间[l, r]的两部分间的影响.感觉说的太多,还是不如具体题目分析,而且题目也不尽相同,记住几句话是没什么用的. 练习地址: http://vjudge.net/contest/view.actio

HDU 5126(stars)四维偏序,cdq分治

题意:给两种操作,进行5万次.操作一:加入一个三维序偶(a,b,c)到集合S里:第二种操作,给两个三维序偶(a1,b1,c1)和(a2,b2,c2),问当前S里有多少个序偶(a,b,c)满足a1<=a<=a2, b1<=b<=b2, c1<=c<=c2.题目保证了a1<=a2,b1<=b2,c1<=c2.所有数在[1,1e9]内 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5126 解法:将操作编号也加入到

【BZOJ3963】[WF2011]MachineWorks cdq分治+斜率优化

[BZOJ3963][WF2011]MachineWorks Description 你是任意性复杂机器公司(Arbitrarily Complex Machines, ACM)的经理,公司使用更加先进的机械设备生产先进的机器.原来的那一台生产机器已经坏了,所以你要去为公司买一台新的生产机器.你的任务是在转型期内尽可能得到更大的收益.在这段时间内,你要买卖机器,并且当机器被ACM公司拥有的时候,操控这些机器以获取利润.因为空间的限制,ACM公司在任何时候都只能最多拥有一台机器. 在转型期内,有若

BZOJ 2225 [Spoj 2371]Another Longest Increasing(CDQ分治)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2225 [题目大意] 给定N个数对(xi,yi),求最长上升子序列的长度. 上升序列定义为{(xi,yi)}满足对i<j有xi<xj且yi<yj. [题解] CDQ分治,将每个区间按照a排序,用区间左边的数据来更新右边的最长上升序列, 为排除a相等但是b上升情况的误统计,在排序时加入下标作为第二关键字, 使得a相等的情况下标小的后更新. [代码] #include <cs

BZOJ 3262: 陌上花开 [CDQ分治 三维偏序]

Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A比另一朵花B要美丽,当且仅当Sa>=Sb,Ca>=Cb,Ma>=Mb.显然,两朵花可能有同样的属性.需要统计出评出每个等级的花的数量. Input 第一行为N,K (1 <= N <= 100,000, 1 <= K <= 200,000 ), 分别表示花的数量和最大属性值. 以下N行,每

HDU 5618:Jam&#39;s problem again(CDQ分治+树状数组处理三维偏序)

http://acm.hdu.edu.cn/showproblem.php?pid=5618 题意:-- 思路:和NEUOJ那题一样的.重新写了遍理解了一下,算作处理三维偏序的模板了. 1 #include <cstdio> 2 #include <algorithm> 3 #include <iostream> 4 #include <cstring> 5 using namespace std; 6 #define INF 0x3f3f3f3f 7 #d

ACdream1157 Segments(CDQ分治 + 线段树)

题目这么说的: 进行如下3种类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段,保证每条插入线段最多插入一次,且这次删除操作一定合法3) Q L R(1 <= L <= R <= 1000000000) 查询目前存在的线段中有多少条线段完全包含[L,R]这个线段,线段X被线段Y完全包含即LY <= LX <= RX <= RY) 初学CDQ分治是看了B

BZOJ 2726: [SDOI2012]任务安排( dp + cdq分治 )

考虑每批任务对后面任务都有贡献, dp(i) = min( dp(j) + F(i) * (T(i) - T(j) + S) ) (i < j <= N)  F, T均为后缀和. 与j有关的量只有t = dp(j) - F(i) * T(j) , 我们要最小化它. dp(j)->y, T(j)->x, 那么y = F(i) * x + t, 就是给一些点和一个斜率...然后最小化截距, 显然维护下凸包就可以了. 然后因为无比坑爹的出题人....时间可以为负数, 所以要用平衡树维护(