bitset 求解高維偏序


求解五維偏序

給定 \(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

后記

reference:


免責聲明!

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



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