首页 > 其他分享 >CDQ分治和三维偏序

CDQ分治和三维偏序

时间:2023-10-17 18:58:21浏览次数:37  
标签:偏序 int 分治 mid 算法 solve CDQ

专题:CDQ 分治

本页面将完整介绍 CDQ 分治。

简介

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

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

CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,它也因此得名。

解决和点对有关的问题

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

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

  1. 找到这个序列的中点 ;
  2. 将所有点对 (i,j)(i,j) 划分为 33 类:
    • 1≤i≤mid,1≤j≤mid 的点对;
    • 1≤i≤mid,mid+1≤j≤n 的点对;
    • mid+1≤i≤n,mid+1≤j≤n 的点对。
  3. 将 (1,n)(1,n) 这个序列拆成两个序列 (1,mid)(1,mid) 和 (mid+1,n)(mid+1,n) 。此时第一类点对和第三类点对都在这两个序列之中;
  4. 递归地处理这两类点对;
  5. 设法处理第二类点对。

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

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

典型例题1:LOJ112/洛谷P3810 三维偏序(陌上开花)

分析:

三维偏序(陌上开花)是 CDQ 分治的经典问题。

假设我们现在写好了 solve (l, r) ,并且通过递归搞定了 solve (l, mid) 和 solve(mid+1,r) 。现在我们要做的,就是统计满足 l≤i≤mid,mid+1≤j≤r 的点对 (i, j)(i,j) 中,有多个点对还满足 i<j,ai​<aj​,bi​<bj​ 的限制条件。

稍微思考一下就会发现,那个 i<j 的限制条件没啥用了:既然 i 比 mid 小, j 比 mid 大,那 i 肯定比 j 要小。 现在还剩下两个限制条件: ai​<aj​ 与 bi​<bj​ , 根据这个限制条件我们就可以枚举 j , 求出有多少个满足条件的 i。

为了方便枚举,我们把 (l,mid) 和 (mid+1,r) 中的点全部按照 a 的值从小到大排个序。之后我们依次枚举每一 个 j , 把所有 ai​<aj​ 的点 i 全部揷入到某种数据结构里(这里我们选择树状数组)。此时只要查询树状数组里有多少个点的 b 值是小于 bj​ 的,我们就求出了对于这个点 j ,有多少个 ii 可以合法匹配它了。

当我们揷入一个 b 值等于 xx 的点时,我们就令树状数组的 xx 这个位置单点 +1+1,而查询树状数组里有多少个点小于 xx 的操作实际上就是在求前缀和,只要我们事先对于所有的 bb 值做了离散化,我们的复杂度就是对的。

对于每一个 j,我们都需要将所有 ai​<aj​ 的点 i 揷入树状数组中。由于所有的 i 和 j 都已事先按照 aa 值排好序, 这样的话只要以双指针的方式在树状数组里揷入点,则对树状数组的揷入操作就能从 O(n2) 次降到 O(n) 次。

通过这样一个算法流程,我们就用 O(nlogn) 的时间处理完了关于第二类点对的信息了。此时算法的时间复杂度 是 T(n)= T( n/2 ) + T( n/2 ) + O( nlogn )= O(nlog2n)。

【三维偏序(陌上开花)-参考代码-CDQ分治】

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=200005;
int n,k,m;

struct node
{
    int x,y,z,id,w;
    bool operator < (const node &A)const{
        if(A.x==x && A.y==y) return z < A.z;
        else if(A.x==x) return y < A.y;
        return x < A.x;
    }
}a[N],b[N],d[N];

int ans[N],c[N],cnt[N];
vector <int> v1,v2[N] ;

int lowbit(int x)
{
    return x & (-x);
}

void add(int x,int v)
{
    for(int i=x;i<N;i+=lowbit(i)) c[i]+=v;
}

int query(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i)) ans+=c[i];
    return ans;
}

void cdq(int l,int r)
{
    if(l==r) return ;
    int mid=(l+r)>>1;
    cdq(l,mid),cdq(mid+1,r);
    int t1=l,t2=mid+1;
    for(int i=l;i<=r;i++)
    {
        if((t1<=mid && a[t1].y<=a[t2].y) || t2>r)
        {
            add(a[t1].z,a[t1].w);
            b[i]=a[t1++];
        }
        else
        {
            cnt[a[t2].id]+=query(a[t2].z);
            b[i]=a[t2++];
        }
    }
    for(int i=l;i<=mid;i++) add(a[i].z,-a[i].w);
    for(int i=l;i<=r;i++) a[i]=b[i];
}

int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].x>>a[i].y>>a[i].z;
        a[i].id=i;
    }
    sort(a+1,a+n+1);
    int num=1;
    for(int i=2;i<=n+1;i++)
    {
        if(a[i].x!=a[i-1].x || a[i].y!=a[i-1].y || a[i].z != a[i-1].z)
        {
            d[++m]=a[i-1];
            d[m].w=num;
            num=1;
            v2[a[i-1].id]=v1;
            v1.clear();
        }
        else
        {
            num++;
            v1.push_back(a[i-1].id) ;
        }
    }
    for(int i=1;i<=m;i++) a[i]=d[i];
    cdq(1,m);
    for(int i=1;i<=m;i++)
    {
        int sz=v2[a[i].id].size();
        for(auto v:v2[a[i].id]) cnt[v]+=cnt[a[i].id]+sz;
        cnt[a[i].id]+=sz;
    }
    for(int i=1;i<=n;i++) ans[cnt[i]]++;
    for(int i=0;i<n;i++) cout<<ans[i]<<'\n';
    return 0;
}

 

 

CDQ分治的限制

  1. 题目允许离线操作
  2. 修改操作对询问的贡献独立,且修改之间互不影响
  3. 修改对答案的贡献是确定的,与判定标准无关

CDQ分治和整体二分

CDQ分治和整体二分都是基于分治的思想,把复杂的问题拆分成许多可以简单求的解子问题。但是这两种算法必须离线处理,不能解决一些强制在线的题目。不过如果题目允许离线的话,这两种算法要比在线解法(如树套树)快很多。

标签:偏序,int,分治,mid,算法,solve,CDQ
From: https://www.cnblogs.com/wenyutao1/p/17770418.html

相关文章

  • cdq 分治
    cdq分治的思路仍是处理跨过当前区间中点的贡献,但递归顺序是左子区间、当前区间、右子区间。这仍然满足处理当前区间时两个子区间的相对顺序不变(但左子区间不一定是有序的)从嵌套数据结构的观点看,cdq分治就是树套树外层树的中序遍历,优点是空间少一个\(\log\)且常数更小LG4093......
  • 点分治
    点分治1.给定一个带边权的树,共有\(m\)个询问,询问距离为\(k\)的点对是否存在做法1:暴力dfs做法2:\(lca\)(时间复杂度\(O(n^2\logn)\))做法3:点分治(时间复杂度\(O(n\logn)\))思路:1.取一个节点\(u\)2.统计经过\(u\)的链经过\(u\)的链两端点必定在不同子树中记录子节......
  • 递归分治法在快速排序中的应用 java以界面的方式实现
    递归分治法在快速排序中的应用 分治法的基本思想§分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。 k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求......
  • 72ed 2023/8/25 点分治学习笔记
    起因&介绍8月22号的T3是道黑,但思路却不算太难,就去打了这是第一次接触点分治,其实之前也有过一道点分治题,叫阴阳,但当时没去改,就一拖拖了半年才学点分治类似于树形DP,但在一些地方上处理有不同就比如在跑过根结点(1),进入处理它的子树时,会将其他的一部分视作没有(emmm大概这个意思,子树......
  • 基本技巧——根号分治 学习笔记
    基本技巧——根号分治学习笔记根号分治与其说是一个算法,更不如说是一种思想(trick)。定义根号分治,是一种对数据进行点分治的分治方式,它的作用是优化暴力算法;类似于分块,但应用范围比分块更广。具体来说,对于所进行的操作,按照某个点\(B\)划分,分为大于\(B\)及小于\(B\)两个部......
  • 线段树分治&可撤销并查集
    可撤销并查集按时间顺序用一个栈维护合并信息,撤销时从栈顶弹出合并信息,恢复原状态。并查集查找祖先时不能路径压缩,只能按秩合并。例题:[ABC302Ex]BallCollector容易想到将\(A_i\)和\(B_i\)之间连边。遍历整棵树,用可撤销并查集维护图。为了进一步求得答案,需要注意到该......
  • bitset 求解高维偏序
    菜,题简单,trick蠢,求别骂。记录今天做题的时候遇到的一个小trick。先看一道题:P3810【模板】三维偏序(陌上花开)。平凡的三维偏序板子,相信大家都会用CDQ/树套树/K-Dtree之类的优秀做法秒了吧!然后看这个题:求五维偏序,\(n\le3\times10^4\),保证每一维这\(n\)个数都是\(n\)......
  • 分治算法
    基本思想:将序列分为\([l,mid]\)和\([mid+1,r]\),然后递归两边,同时再计算\([l,mid]\)与\([mid+1,r]\)影响所产生的答案(满足单调性的话一般使用走指针)。二维偏序首先将所有元素按\(x,y\)排序。然后递归两边,随后用两个指针\(i\)和\(j\),\(i\)从\(l\)到\(mid\),\(j\)......
  • <学习笔记>线段树分治
    一种离线处理方法可以处理“具体哪个修改对询问有影响”、可以贡献不独立、可以支持插入删除。例题对这道题来说,对修改开线段树,线段树上每个节点开一个\(vector\)来维护出现在这段区间的线段,加入一个线段的区间,直接在区间查询时对所包含的节点压入这条线段就可以。然后从根......
  • 【边分治】P4565 [CTSC2018] 暴力写挂
    初涉边分治。大致就是按照一条边的两端来分类出两套点,然后对这个进行分治,有点是只用考虑两类集合,考虑贡献很简单!!反正就是比点分强。但是如果直接分治是假的,会被菊花图薄纱,需要进行对树的三度化,这个是左儿子右兄弟,但是貌似很少人叫这个,比较不适,然后我们就可以做了。建出来的边分......