求解五維偏序
給定 \(n(\le 3\times 10^4)\) 個五元組,對於每個五元組 \((a_i, b_i, c_i, d_i, e_i)\),求存在多少個 \(1\le j\le n\) 滿足 \(a_i > a_j\) 且 \(b_i > b_j\) 且 \(c_i > c_j\) 且 \(d_i > d_j\) 且 \(e_i > e_j\)。保證每一維都是 \(1\cdots n\) 的排列。
第一感覺
傳統的做法有 cdq 分治或 樹套樹,但是在本題中復雜度會高達 \(O(n\log^4 n)\),更何況這些做法需要嵌套,代碼難度極大。
如果用 K-D tree 則只能有 \(O(n^{\frac{2k - 1}{k}})\) 的 優秀 效率。
於是這里介紹一種使用 bitset 的簡單做法。
用 01矩陣 表示大小關系
我們先對於五元組的 \(a\) 維構造出一個 \(n \times n\) 的 01 方陣 \(A\)。
對於 \(A\) 第 \(i\) 行第 \(j\) 列的元素 \(A_{i, j}\),若為 1 則表示 \(a_i > a_j\), 反之則表示 \(a_i \le a_j\)。
換言之,方陣 \(A\) 存儲着 \(n\) 個五元組在 \(a\) 維上的大小關系。
同理有矩陣 \(B, C, D, E\)。
接下來我們考慮 \(S = A \ \And \ B \ \And \ C \ \And \ D \ \And \ E\)(\(\And\) 表示按位與)的實際意義。
顯然 \(S_{i, j}\) 就表示 第 \(i\) 個和第 \(j\) 個五元組在所有五維意義上的偏序關系。
要求第 \(i\) 個的答案,只要求 \(S_{i, 1} \sim S_{i, n}\) 間中 1 的個數即可。
關於 bitset
如何構造這個矩陣?這就需要強大的 bitset
了!
首先我們來了解一下 bitset。這是 C++ 中的一種 STL。它類似於 bool 數組,每個位置只有兩種值:0 或 1。
bitset 的實現方式是壓位,那么一個大小為 \(n\) 的 bitset 的空間復雜度為 \(O(\frac{1}{\omega} n)\)。其中 \(\omega = 32\) 或 \(64\)(系統位數)。
一些基本操作:
bitset<N> f; // 定義一個大小為 N 的 bitset,下標范圍為 [0, N)
f.set(i); // 在下標 i 處置為 1
f.reset(i); // 在下標 i 處置為 0
f.test(i); // 判斷下標 i 處是否為 1
f[i]; // 在下標 i 處取值
除了構造函數,其他操作的復雜度均為 \(O(1)\)。
但還有功能更強大的:
f.set(); // 全部置為 1
f.reset(); // 全部置為 0
f = g; // bitset 賦值
f &= g; // 將 f 對 g 做按位與操作
f |= g; // 將 f 對 g 做按位或操作
f ^= g; // 將 f 對 g 做按位異或操作
// 以及各種位運算操作
f.count(); // 計算 bitset 中 1 的個數
這些操作都是 \(O(\frac{1}{\omega} n)\) 的時間復雜度。
bitset 的優秀之處,就在於時空復雜度中 \(\dfrac{1}{\omega}\) 的優秀常數。
關系矩陣的構造
下面講一講 \(S\) 矩陣的構造算法。
我們對於每一維,將五元組升序排序,然后用一個中間變量 tmp
表示當前維度下,滿足當前范圍的點的點集(tmp 就是一個 bitset)。
當做到第 \(i\) 個五元組時,我們先在第 \(i\) 個五元組所對應的 bitset f[point[i].index]
對 tmp
做按位與操作。
然后在 tmp 的當前五元組的編號處置為 1。
每一維都這樣做下去即可。
最后使用 count
函數統計答案即可。
核心代碼
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : bitset 求解較高維偏序
*/
for (register int k = 0; k < K; k++) {
cmp::set(k); // cmp 函數設置維度
sort(point, point + n, cmp::f); // 排序
tmp.reset(); // 清空 tmp
for (register int i = 0; i < n; i++) {
if (!k) f[point[i].index] = tmp; // 第一維特殊處理——直接賦值
else f[point[i].index] &= tmp; // 按位與操作
tmp.set(point[i].index); // 在當前五元組編號處置 1
}
}
for (register int i = 0; i < n; i++)
cout << f[i].count() << endl; // 統計答案
不難發現上面的時空復雜度都是 \(O(\frac{1}{\omega} n^2)\) 的。雖說也是平方級別的算法,但由於 bitset 的優秀常數,在實際中運行效率很不錯。
習題
HihoCoder #1513 小Hi的煩惱:https://hihocoder.com/problemset/problem/1513
更高維的偏序問題
給定 \(n(\le 5\times 10^4)\) 個 \(k(\le 7)\) 元組,對於每個 \(k\) 元組 \(T_i = (v_1, v_2, \cdots, v_k)_i\),求存在多少個 \(1\le j\le n\) 滿足 \(i \succ j\)。保證每一維都是 \(1\cdots n\) 的排列。
空間限制:64 MB
此題開大的 \(n\) 的范圍,維數,並加大了對空間的要求。
很顯然 \(O(\frac{1}{\omega} n^2)\) 的空間復雜度以及遠遠無法符合要求了。
注意這里優化的是空間,時間復雜度不變。
分塊優化空間
我們對於每一維進行值域上的分塊,塊長 \(b = \lceil\sqrt{n}\rceil\)。
我們定義: bitset<N> dat[K][T];
dat[k][i]
表示在第 \(k\) 維,處於 前 \(i\) 塊的值域范圍內(即值域 \(\in [1, i\times b]\) ) 的點的集合。
這個可以在 \(O(n^{1.5}k)\) 的時間預處理。
那么如何通過這個信息獲取關於 \(T_i\) 的信息呢?
仍然是分塊的經典思想——整塊取現成,散塊暴力直接干。
具體的,對於第 \(k\) 維為 \(v\) 的情況:
- 整塊:直接取出塊 \([1, \lfloor\frac{v}{b}\rfloor]\) 的信息(
dat[k][p];
)。
- 時間復雜度:\(O(\frac{1}{\omega} n)\)。 - 散塊:暴力掃出值域在 \((\lfloor\frac{v}{b}\rfloor \times b + 1, v]\) 的編號。
- 時間復雜度:\(O(\sqrt{n})\)
最后對所有維度的 bitset 做按位與,使用 count
函數求解答案。這里時間復雜度為 \(O(\frac{1}{\omega} nk)\)。
對所有 \(n\) 個 \(k\) 元組都可以這樣搞。
總時間復雜度為 \(O(\frac{1}{\omega}n^2 k)\),似乎並沒有優化。但空間效率得到了不錯的提升——\(O(\frac{1}{\omega} n^{1.5}k)\)
核心代碼
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : bitset 求解較高維偏序
*/
// rank[k][v] 表示 k 維中值為 v 的點的編號
for (register int k = 0; k < K; k++)
for (register int i = 1; i * b <= n; i++)
for (register int j = 1; j <= i * b; j++)
dat[k][i].set(rank[k][j]); // 分塊預處理
for (register int i = 1; i <= n; i++) {
bitset<N> ans, tmp;
ans.set(); // 一開始設為全 1(按位與操作)
for (register int k = 0; k < K; k++) {
tmp.reset(); // 每一維都要重置
int p = point[k][i] / b; // 計算整塊的范圍
tmp |= dat[k][p]; // 整塊取現成
for (register int j = p * b + 1; j <= point[k][i]; j++)
tmp.set(rank[k][j]); // 暴力掃散塊
ans &= tmp; // 對每一維按位與
}
cout << ans.count() - 1 << endl; // 統計答案
}
習題
HihoCoder #1236 Scores:http://hihocoder.com/problemset/problem/1236
后記
- 原文地址:https://www.cnblogs.com/-Wallace-/p/13293541.html
- 本文作者:@-Wallace-
- 轉載請附上出處。
reference: