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:

posted @ 2020-07-13 17:06  -Wallace-  阅读(2261)  评论(2编辑  收藏  举报