【教程】CDQ套CDQ——四维偏序问题【转载】

转自

前言

上一篇文章已经介绍了简单的CDQ分治,包括经典的二维偏序和三维偏序问题,还有带修改和查询的二维/三维偏序问题。本文讲介绍多重CDQ分治的嵌套,即多维偏序问题。

四维偏序问题

      给定N(N<=20000)个有序四元组(a,b,c,d),求对于每一个四元组(a,b,c,d),有多少个四元组(a2,b2,c2,d2)满足a2<a && b2<b && c2<c && d2<d

       不需要太多思考,就能得到一个O(nlog^3n)算法:先按照a元素排序,然后进行CDQ分治,在合并的时候按照b元素的升序对两个子问题进行合并(对这个操作,我们暂且称为“对a分治,按照b合并”)。这样,我们需要一个能进行以下操作的数据结构:

  1. 插入一个二元组(c,d);
  2. 给出一个二元组(c,d),询问有多少个二元组(c2,d2)满足c2<c && d2<d。

这个问题很容易用树套树解决,但是树套树巨大的常数和空间消耗往往是性能的瓶颈。记得我们之前说过,CDQ分治能代替复杂的数据结构,并将问题“降维”。这里,我们就用双重嵌套的CDQ分治,把这个问题继续降维,避免使用树套树。

回忆二维偏序问题,我们对第一维分治之后,所有的二元组被我们划分为了左右两部分,左边和右边各自的内部问题已经通过递归解决,剩下要考虑的就是左边的修改对右边的查询的影响。我们不妨把分治后的二元组重新标记一下,左边的为(L,b),右边的为(R,b)。这时候,(a1,b1)对(a2,b2)有影响,当且仅当a1 == L && a2 == R && b1 < b2。然后我们按照b的顺序合并,解决了这一问题。

对于三维偏序问题也是一样的,对第一维分治并且重新标记之后,只有(L,b1,c1)可能对(R,b2,c2)有影响。我们用“按顺序归并”保证b元素的顺序,用树状数组保证c元素的顺序。

对于四维偏序问题,我们也按照这样的思路进行下去。对第一维分治,并把所有元素重新标记为(L,b,c,d)和(R,b,c,d),然后按照b的顺序合并。注意,我们在这里只是做合并,并不用任何数据结构对c和d加以维护。

合并完之后,我们得到了一个按照b值升序排列的序列,现在,我们把这个序列复制一份,用CDQ分治统计刚刚我们没有统计的信息——左边的修改对右边的查询的影响

这时候这个序列仅仅是b值有序,但是a值是杂乱无章的,不过我们之前已经对a值进行了重新标记,现在a值只可能是L或者R。

我们对b值进行分治,递归处理左右两边的子问题(别忘了我们现在要处理的问题是“在第一维分治之后,左边的修改对右边的查询的影响”)。然后,把所有b值也重新标号为L和R,于是我们得到了这样一个序列(L/R,L/R,c,d)。注意,现在只有(L,L,c,d)可能对(R,R,c,d)产生影响!请读者仔细考虑这个条件,这是理解多重CDQ分治的关键!

然后我们按照c值从小到大进行合并,这保证了统计时c值的顺序,同时用树状数组维护d值的信息,保证考虑到d值的顺序。只有一个元素为(L,L,c,d)的时候,它才可能影响到后面的查询;只有一个元素为(R,R,c,d)的时候,它才可能收到前面的修改的影响。即,我们在归并的时候,把一个d值加入树状数组,当且仅当这个四元组的a == L && b == L;我们向树状数组查询d值的信息并应用到这个查询上面,当且仅当这个四元组的a == R && b == R。

让我们总结一下全过程:

  1. 对第一维进行排序。
  2. 对第一维重新标号,然后对第一维分治,递归解决子问题,按照第二维的顺序合并。此时只是单纯的合并,并不进行统计。
  3. 把合并后的序列复制一份,并对第二维重新标号,在复制的那一份中进行CDQ分治。即对第二维分治,递归解决子问题,按照第三维的顺序合并。合并过程中用树状数组维护第四维的信息。

下面是[HZOI 2016]偏序 COGS 2479的AC代码:

  1 #include <iostream>
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <cstdlib>
  5 #include <cstdio>
  6 #include <cmath>
  7
  8 using namespace std;
  9 typedef long long ll;
 10 const int MAXN = 50002;
 11
 12 int n;
 13
 14 struct Item {
 15     int d1,d2,d3,d4,part; // 分别表示每一维的数据,part为第一维重标号之后的值
 16 }a[MAXN];
 17 const int LEFT = 0;
 18 const int RIGHT = 1;
 19
 20 namespace BIT { // 树状数组相关
 21     int arr[MAXN];
 22     inline int lowbit( int num ) { return num&(-num); }
 23     void add( int idx ) {
 24         for( ; idx <= n; idx += lowbit(idx) ) arr[idx]++;
 25     }
 26     int query( int idx ) {
 27         int ans = 0;
 28         for( ; idx; idx -= lowbit(idx) ) ans += arr[idx];
 29         return ans;
 30     }
 31     void clear( int idx ) {
 32         for( ; idx <= n; idx += lowbit(idx) ) arr[idx] = 0;
 33     }
 34 }
 35
 36 ll ans = 0;
 37
 38 Item tmp3d[MAXN];
 39 Item tmp2d[MAXN];
 40 void cdq3d( int L, int R ) { // 对第二维分治,按照第三维合并
 41     if( R-L <= 1 ) return;
 42     int M = (L+R)>>1; cdq3d(L,M); cdq3d(M,R);
 43     int p = L, q = M, o = L;
 44     while( p < M && q < R ) { // 因为第二维是“左边全都是L,右边全都是R”,所以略去第二维的标号
 45         if( tmp2d[p].d3 < tmp2d[q].d3 ) {
 46             if( tmp2d[p].part == LEFT ) BIT::add( tmp2d[p].d4 );
 47             tmp3d[o++] = tmp2d[p++];
 48         } else {
 49             if( tmp2d[q].part == RIGHT ) ans += BIT::query( tmp2d[q].d4 );
 50             tmp3d[o++] = tmp2d[q++];
 51         }
 52     }
 53     while( p < M ) tmp3d[o++] = tmp2d[p++];
 54     while( q < R ) {
 55         if( tmp2d[q].part == RIGHT ) ans += BIT::query( tmp2d[q].d4 );
 56         tmp3d[o++] = tmp2d[q++];
 57     }
 58     for( int i = L; i < R; ++i ) { // 清空树状数组
 59         if( tmp3d[i].part == LEFT ) BIT::clear( tmp3d[i].d4 );
 60         tmp2d[i] = tmp3d[i];
 61     }
 62 }
 63 void cdq2d( int L, int R ) { // 对第一维分治,按照第二维合并
 64     if( R-L <= 1 ) return;
 65     int M = (L+R)>>1; cdq2d(L,M); cdq2d(M,R);
 66     int p = L, q = M, o = L;
 67     while( p < M && q < R ) {
 68         if( a[p].d2 < a[q].d2 ) {
 69             a[p].part = LEFT; // 重标号
 70             tmp2d[o++] = a[p++];
 71         } else {
 72             a[q].part = RIGHT;
 73             tmp2d[o++] = a[q++];
 74         }
 75     }
 76     while( p < M ) {
 77         a[p].part = LEFT;
 78         tmp2d[o++] = a[p++];
 79     }
 80     while( q < R ) {
 81         a[q].part = RIGHT;
 82         tmp2d[o++] = a[q++];
 83     }
 84     for( int i = L; i < R; ++i ) a[i] = tmp2d[i]; // tmp2d为“复制的那一份”
 85     cdq3d(L,R);
 86 }
 87
 88 int main() {
 89     freopen( "partial_order.in", "r", stdin );
 90     freopen( "partial_order.out", "w", stdout );
 91     scanf( "%d", &n );
 92     for( int i = 0; i < n; ++i ) {
 93         a[i].d1 = i;
 94         scanf( "%d", &a[i].d2 );
 95     }
 96     for( int i = 0; i < n; ++i ) scanf( "%d", &a[i].d3 );
 97     for( int i = 0; i < n; ++i ) scanf( "%d", &a[i].d4 );
 98     cdq2d(0,n); printf( "%lld\n", ans );
 99     return 0;
100 }

习题

[HZOI 2016]偏序 COGS 2479

原文地址:https://www.cnblogs.com/Turkeyghb/p/8206568.html

时间: 2025-01-01 02:58:15

【教程】CDQ套CDQ——四维偏序问题【转载】的相关文章

HDU - 5126: stars (求立方体内点数 CDQ套CDQ)

题意:现在给定空空的三维平面,有加点操作和询问立方体点数. 思路:考虑CDQ套CDQ.复杂度是O(NlogN*logN*logN),可以过此题. 具体的,这是一个四维偏序问题,4维分别是(times,x,y,z):我们知道cdq可以求出t<=T,x=X,y<=Y,在套一层就可以z<=Z了.那么一个立方体,我们拆为8个点来容斥. 然后现在的问题就是,求出(0,0,0)到(x,y,z)的点数. 第一维T已经默认排序了,我们先对X分治. 把所有问题分成两块,并且把左边这块标记o=-1,右边的标

HDU5126---stars (CDQ套CDQ套 树状数组)

题意:Q次操作,三维空间内 每个星星对应一个坐标,查询以(x1,y1,z1) (x2,y2,z2)为左下顶点 .右上顶点的立方体内的星星的个数. 注意Q的范围为50000,显然离散化之后用三维BIT会MLE. 我们可以用一次CDQ把三维变成二维,变成二维之后就有很多做法了,树套树,不会树套树的话还可以继续CDQ由二维变成一维,,变成一维了就好做了,,最基本的数据结构题目了.. 不得不说.CDQ真的很神奇. 下面做法就是CDQ套CDQ套树状数组. 1 #include <cstdio> 2 #i

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 解法:将操作编号也加入到

四维偏序

我是萌萌的传送门 终于(算是)学会CDQ套CDQ了= =看来我对分治的理解还是不太深,要不然怎么会只会用CDQ压掉一维却不会压掉更高维呢-- 题目就是个最简单的四维偏序,为了简化问题把四维都搞成了1~n的排列(废话,出题人可是我自己= =),还把四维LIS换成了四维正序对(该是有多懒--) 四维偏序的话其实压力还是有点大的,首先O(nlog3n)的算法跟暴力差距真的不是特别大,不过经实测除了树套树套树速度和暴力差不多之外剩下的两种做法都是比较快的(经测试n=50000的数据暴力要跑大概6s+,好

COGS 2479. [HZOI 2016]偏序 [CDQ分治套CDQ分治 四维偏序]

传送门 给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 对于100%的数据,1<=n<=50000,保证所有的ai.bi.ci分别组成三个1~n的排列. $CDQ$分治套$CDQ$分治也不是很难嘛 对于本题,设四维$a,b,c,d$ $Sort\ at\ a$ $CDQ(l,r)$ $\quad CDQ(l,mid)$ $\quad CDQ(mid+1,r)$ $\

论如何优雅的用bitset来求四维偏序

四维偏序.. 就是给你一个四维集合.再给你一些询问,请你求出a[i].x1<=ask.x1&&a[i].x2<=ask.x2&&a[i].x3<=ask.x3&&a[i].x4<=ask.x4的个数.. 集合大小<=30000 询问个数<=30000 然后怎么做呢?? 其实很简单只要排序+cdq+树状数组套平衡树什么的就行了 qnmd老子不会.. 这时! 神器来了! 那就是bitset! 众所周知,bitset能存一堆二进

COGS2479(四维偏序)

题意:给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 分析:cdq分治套cdq分治 对于四维偏序,可以先对第一维排序,然后对第一维分治,按照第二维顺序合并 即cdq(l,r)表示分治第一维,然后把这里面的按照第二维的顺序从小到大排序 然后问题就变成了三维偏序,再套一个cdq2(l,r)去更新答案 不过要注意一点很关键的,那就是按照第二维排序后,第一维已经是乱序了 根据cdq分治

hdu5618(cdq分治求三维偏序)

题意:给n(n<=100000)个点,坐标为(xi,yi,zi)(1<=xi,yi,zi<=100000),定义一个点A比一个点B小,当且仅当xA<=xB,yA<=yB,zA<=zB.求对于每个点,有多少个点比它小. 分析: 首先肯定按照x递增顺序排个序 接下来就是每次往平面插入一个点,求这个点左下方已经有多少个点,这可以用二维树状数组来搞,但是很明显会爆空间,不可以接受(当然树套树也是不可以的) 可以考虑对第二维cdq分治 对于一个区间[l,r],先递归区间[l,mi

SPOJ:Another Longest Increasing Subsequence Problem(CDQ分治求三维偏序)

Given a sequence of N pairs of integers, find the length of the longest increasing subsequence of it. An increasing sequence A1..An is a sequence such that for every i < j, Ai < Aj. A subsequence of a sequence is a sequence that appears in the same