【学习笔记】使用 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
后记
- 原文地址:https://www.cnblogs.com/-Wallace-/p/13293541.html
- 本文作者:@-Wallace-
- 转载请附上出处。
reference:
【学习笔记】使用 bitset 求解较高维偏序问题的更多相关文章
- Sparse Filtering 学习笔记(三)目标函数的建立和求解
Sparse Filtering 是一个用于提取特征的无监督学习算法,与通常特征学习算法试图建模训练数据的分布的做法不同,Sparse Filtering 直接对训练数据的特征分布进行分析,在所谓 ...
- GMM高斯混合模型学习笔记(EM算法求解)
提出混合模型主要是为了能更好地近似一些较复杂的样本分布,通过不断添加component个数,能够随意地逼近不论什么连续的概率分布.所以我们觉得不论什么样本分布都能够用混合模型来建模.由于高斯函数具有一 ...
- V-rep学习笔记:Geometric Constraint Solver(几何约束求解)
The geometric constraint solver is slower and less precise at solving kinematic problems, but might ...
- matlab学习笔记11_3高维数组操作 filp, shiftdim, size, permute, ipermute
一起来学matlab-matlab学习笔记11 11_3 高维数组处理和运算 filp, shiftdim, size, permute, ipermute 觉得有用的话,欢迎一起讨论相互学习~Fol ...
- matlab学习笔记11_2高维数组操作 squeeze,ind2sub, sub2ind
一起来学matlab-matlab学习笔记11 11_2 高维数组处理和运算 squeeze, ind2sub, sub2ind 觉得有用的话,欢迎一起讨论相互学习~Follow Me squeeze ...
- OI知识点|NOIP考点|省选考点|教程与学习笔记合集
点亮技能树行动-- 本篇blog按照分类将网上写的OI知识点归纳了一下,然后会附上蒟蒻我的学习笔记或者是我认为写的不错的专题博客qwqwqwq(好吧,其实已经咕咕咕了...) 基础算法 贪心 枚举 分 ...
- SVM学习笔记(一)
支持向量机即Support Vector Machine,简称SVM.一听这个名字,就有眩晕的感觉.支持(Support).向量(Vector).机器(Machine),这三个毫无关联的词,硬生生地凑 ...
- TensorFlow学习笔记——深层神经网络的整理
维基百科对深度学习的精确定义为“一类通过多层非线性变换对高复杂性数据建模算法的合集”.因为深层神经网络是实现“多层非线性变换”最常用的一种方法,所以在实际中可以认为深度学习就是深度神经网络的代名词.从 ...
- CTR学习笔记&代码实现1-深度学习的前奏LR->FFM
CTR学习笔记系列的第一篇,总结在深度模型称王之前经典LR,FM, FFM模型,这些经典模型后续也作为组件用于各个深度模型.模型分别用自定义Keras Layer和estimator来实现,哈哈一个是 ...
随机推荐
- 从头学起Verilog(三):Verilog逻辑设计
引言 经过了组合逻辑和时序逻辑的复习,终于到了Verilog部分.这里主要介绍Verilog一些基础内容,包括结构化模型.TestBench编写和仿真.真值表模型. 这部分内容不多,也都十分基础,大家 ...
- .NET 5 带来的新特性 [MemberNotNull] 与 [MemberNotNullWhen]
MemberNotNullAttribute是 .NET 5 的新增特性,位于System.Diagnostics.CodeAnalysis.该特性用于显式声明,调用此方法后该值不再为 Null.示例 ...
- pikachs 渗透测试1-环境及暴力破解
一.安装 PhpStudy20180211,默认安装 1.mysql默认密码是root,因为在虚拟机,保留不动 2.解压pikachs 到 C:\phpStudy\PHPTutorial\WWW\pi ...
- 企业级工作流解决方案(十二)--集成Abp和ng-alain--用户身份认证与权限验证
多租户 如果系统需要支持多租户,那么最好事先定义好多租户的存储部署方式,Abp提供了几种方式,根据需要选择,每一个用户身份认证与权限验证都需要完全的隔离 这里设计的权限数据全部存储在缓存中,每个租户单 ...
- 图片恢复有新招,EasyRecovery预览模式助你快速恢复
EasyRecovery作为一款数据恢复软件,因其便捷的操作.低廉的价格深受大家的喜爱.EasyRecovery具有"傻瓜式"操作,就算你是第一次接触这款软件,通过主页提示也能很快 ...
- mac下让iterm2记住远程ssh连接
brew安装sshpass brew install http://git.io/sshpass.rb 在根目录下建立passowrd目录用来管理密码,vim testserver 输入明文密码,保存 ...
- CENTOS 7平滑升级PHP到最新版7.3
安装Remi和EPEL数据源(仓库) rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm r ...
- JavaScript中的链式调用
链模式 链模式是一种链式调用的方式,准确来说不属于通常定义的设计模式范畴,但链式调用是一种非常有用的代码构建技巧. 描述 链式调用在JavaScript语言中很常见,如jQuery.Promise等, ...
- CSS3 学习笔记(上)
一.CSS简介 CSS(Cascading Style Sheets)层叠样式表.其中,样式定义为如何显示HTML元素,它通常储存在样式表,将样式添加到HTML中,能够解决内容与表现分离的问题.由于网 ...
- 【mq读书笔记】消息消费队列和索引文件的更新
ConsumeQueue,IndexFile需要及时更新,否则无法及时被消费,根据消息属性查找消息也会出现较大延迟. mq通过开启一个线程ReputMessageService来准时转发commitL ...