給定 \(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\) 的排列。此題開大的 \(n\) 的範圍,維數,並加大了對空間的要求。空間限制:64 mb
很顯然 \(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發現,在 能承受的範圍內,筆記本的記憶體和速度是不可兼得的。可是,有一些筆記本是被另外一些 完虐 的,也就是記...