【教程】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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM