前言
上一篇文章已經介紹了簡單的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合並”)。這樣,我們需要一個能進行以下操作的數據結構:
- 插入一個二元組(c,d);
- 給出一個二元組(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。
讓我們總結一下全過程:
- 對第一維進行排序。
- 對第一維重新標號,然后對第一維分治,遞歸解決子問題,按照第二維的順序合並。此時只是單純的合並,並不進行統計。
- 把合並后的序列復制一份,並對第二維重新標號,在復制的那一份中進行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