bitset 求解高維偏序

2022-05-03 07:27:07 字數 3745 閱讀 2176

給定 \(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^})\) 的 優秀 效率。

於是這裡介紹一種使用 bitset 的簡單做法。

我們先對於五元組的 \(a\) 維構造出乙個 \(n \times n\) 的 01 方陣 \(a\)。

對於 \(a\) 第 \(i\) 行第 \(j\) 列的元素 \(a_\),若為 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\) 個的答案,只要求 \(s_ \sim s_\) 間中 1 的個數即可。

如何構造這個矩陣?這就需要強大的bitset了!

首先我們來了解一下 bitset。這是 c++ 中的一種 stl。它類似於 bool 陣列,每個位置只有兩種值:0 或 1。

bitset 的實現方式是壓位,那麼乙個大小為 \(n\) 的 bitset 的空間複雜度為 \(o(\frac n)\)。其中 \(\omega = 32\) 或 \(64\)(系統位數)。

一些基本操作:

bitsetf; // 定義乙個大小為 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 n)\) 的時間複雜度。

bitset 的優秀之處,就在於時空複雜度中 \(\dfrac\) 的優秀常數。

下面講一講 \(s\) 矩陣的構造演算法。

我們對於每一維,將五元組公升序排序,然後用乙個中間變數tmp表示當前維度下,滿足當前範圍的點的點集(tmp 就是乙個 bitset)。

當做到第 \(i\) 個五元組時,我們先在第 \(i\) 個五元組所對應的 bitsetf[point[i].index]tmp做按位與操作。

然後在 tmp 的當前五元組的編號處置為 1。

每一維都這樣做下去即可。

最後使用count函式統計答案即可。

/*

* author : _wallace_

* source :

* problem : bitset 求解較高維偏序

*/for (register int k = 0; k < k; k++)

}for (register int i = 0; i < n; i++)

cout << f[i].count() << endl; // 統計答案

不難發現上面的時空複雜度都是 \(o(\frac n^2)\) 的。雖說也是平方級別的演算法,但由於 bitset 的優秀常數,在實際中執行效率很不錯。

hihocoder #1513 小hi的煩惱:

給定 \(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 n^2)\) 的空間複雜度以及遠遠無法符合要求了。

注意這裡優化的是空間,時間複雜度不變。

我們對於每一維進行值域上的分塊,塊長 \(b = \lceil\sqrt\rceil\)。

我們定義:bitsetdat[k][t];dat[k][i]表示在第 \(k\) 維,處於前 \(i\) 塊的值域範圍內(即值域 \(\in [1, i\times b]\) )的點的集合。

這個可以在 \(o(n^k)\) 的時間預處理。

那麼如何通過這個資訊獲取關於 \(t_i\) 的資訊呢?

仍然是分塊的經典思想——整塊取現成,散塊暴力直接幹。

具體的,對於第 \(k\) 維為 \(v\) 的情況:

最後對所有維度的 bitset 做按位與,使用count函式求解答案。這裡時間複雜度為 \(o(\frac nk)\)。

對所有 \(n\) 個 \(k\) 元組都可以這樣搞。

總時間複雜度為 \(o(\fracn^2 k)\),似乎並沒有優化。但空間效率得到了不錯的提公升——\(o(\frac n^k)\)

/*

* author : _wallace_

* source :

* 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++)

cout << ans.count() - 1 << endl; // 統計答案

}

hihocoder #1236 scores:

reference:

bitset做多維偏序

很久以前就聽說這個大名鼎鼎的東西 暴力 了,現在才去寫。其實很簡單,對每一維排序,從左到右掃,維護乙個bitset表示這位之前的某個點是否出現 即出現則該位為1否則0 查詢某個數時則在每一維的排序完陣列二分找到最右邊 這個數這一維權值的位置,取出該位的bitset,將每一維的bitset與起來即可得...

二維偏序問題

想學cdq分治,然而cdq的經典題目是三維偏序問題,是建立在二維偏序問題的基礎上的。我這只蒟蒻連個二維偏序問題都沒做過。在網上找了一大圈,才勉強找到乙個二維偏序的題目。bzoj的許可權啊。是時候買個許可權號了。題目鏈結 這個牛客網也不知道是什麼鬼。給你n個物品,每個物品有兩個參量,分別為 s,w 讓...

Laptop 二維偏序

fst是一名可憐的小朋友,他很強,但是經常fst,所以rating一直低迷。但是重點在於,他非常適合acm!並在最近的區域賽中獲得了不錯的成績。拿到獎金後fst決定買一台新筆記本,但是fst發現,在 能承受的範圍內,筆記本的記憶體和速度是不可兼得的。可是,有一些筆記本是被另外一些 完虐 的,也就是記...