zoukankan      html  css  js  c++  java
  • 【教程】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
    ", ans );
     99     return 0;
    100 }

    习题

           [HZOI 2016]偏序 COGS 2479

  • 相关阅读:
    How to Install Linux, Apache, MySQL, PHP (LAMP) stack on CentOS 6 【Reliable】
    可以把一些常用的方法,写入js文件,引入html界面
    把功能写在方法里,函数化,方法化
    那些SQL语句
    Linux&shell之高级Shell脚本编程-创建菜单
    Linux&shell之高级Shell脚本编程-创建函数
    PHP isset()与empty()的使用区别详解
    如何打开mo文件并修改 PoEdit
    Linux&shell之如何控制脚本
    Linux&shell之显示数据
  • 原文地址:https://www.cnblogs.com/mlystdcall/p/6232324.html
Copyright © 2011-2022 走看看