bitset
介绍
std::bitset
是标准库中的一个存储 0/1
的大小不可变容器。严格来讲,它并不属于 STL。
bitset 与 STL
The C++ standard library provides some special container classes, the so-called container adapters (stack, queue, priority queue). In addition, a few classes provide a container-like interface (for example, strings, bitsets, and valarrays). All these classes are covered separately.1 Container adapters and bitsets are covered in Chapter 12.
The C++ standard library provides not only the containers for the STL framework but also some containers that fit some special needs and provide simple, almost self-explanatory, interfaces. You can group these containers into either the so-called container adapters, which adapt standard STL containers to fit special needs, or a bitset, which is a containers for bits or Boolean values. There are three standard container adapters: stacks, queues, and priority queues. In priority queues, the elements are sorted automatically according to a sorting criterion. Thus, the "next" element of a priority queue is the element with the "highest" value. A bitset is a bitfield with an arbitrary but fixed number of bits. Note that the C++ standard library also provides a special container with a variable size for Boolean values: vector.
——摘自《The C++ Standard Library 2nd Edition》
由此看来,bitset
并不属于 STL,而是一种标准库中的 "Special Container"。事实上,它作为一种容器,也并不满足 STL 容器的要求。说它是适配器,它也并不依赖于其它 STL 容器作为底层实现。
由于内存地址是按字节即 byte
寻址,而非比特 bit
,一个 bool
类型的变量,虽然只能表示 0/1
, 但是也占了 1 byte 的内存。
bitset
就是通过固定的优化,使得一个字节的八个比特能分别储存 8 位的 0/1
。
对于一个 4 字节的 int
变量,在只存 0/1
的意义下,bitset
占用空间只是其 \(\frac{1}{32}\),计算一些信息时,所需时间也是其 \(\frac 1{32}\)。
在某些情况下通过 bitset
可以优化程序的运行效率。至于其优化的是复杂度还是常数,要看计算复杂度的角度。一般 bitset
的复杂度有以下几种记法:(设原复杂度为 \(O(n)\))
- \(O(n)\),这种记法认为
bitset
完全没有优化复杂度。 - \(O(\frac n{32})\),这种记法不太严谨(复杂度中不应出现常数),但体现了
bitset
能将所需时间优化至 \(\frac 1{32}\)。 - \(O(\frac n w)\),其中 \(w=32\)(计算机的位数),这种记法较为普遍接受。
- \(O(\frac n {\log w})\),其中 \(w\) 为计算机一个整型变量的大小。
当然,vector
的一个特化 vector<bool>
的储存方式同 bitset
一样,区别在于其支持动态开空间,bitset
则和我们一般的静态数组一样,是在编译时就开好了的。
然而,bitset
有一些好用的库函数,不仅方便,而且有时可以避免使用 for 循环而没有实质的速度优化。因此,一般不使用 vector<bool>
。
使用
头文件
C++ | |
---|---|
1 |
|
指定大小
C++ | |
---|---|
1 |
|
构造函数
bitset()
: 每一位都是false
。bitset(unsigned long val)
: 设为val
的二进制形式。bitset(const string& str)
: 设为 \(01\) 串str
。
运算符
operator []
: 访问其特定的一位。operator ==/!=
: 比较两个bitset
内容是否完全一样。operator &/&=/|/| =/^/^=/~
: 进行按位与/或/异或/取反操作。bitset
只能与bitset
进行位运算,若要和整型进行位运算,要先将整型转换为bitset
。operator <>/<<=/>>=
: 进行二进制左移/右移。operator <>
: 流运算符,这意味着你可以通过cin/cout
进行输入输出。
成员函数
count()
: 返回true
的数量。size()
: 返回bitset
的大小。test(pos)
: 它和vector
中的at()
的作用是一样的,和[]
运算符的区别就是越界检查。any()
: 若存在某一位是true
则返回true
,否则返回false
。none()
: 若所有位都是false
则返回true
,否则返回false
。all()
:C++11,若所有位都是true
则返回true
,否则返回false
。-
set()
: 将整个bitset
设置成true
。set(pos, val = true)
: 将某一位设置成true
/false
。
-
reset()
: 将整个bitset
设置成false
。reset(pos)
: 将某一位设置成false
。相当于set(pos, false)
。
-
flip()
: 翻转每一位。(\(0\leftrightarrow1\),相当于异或一个全是 \(1\) 的bitset
)flip(pos)
: 翻转某一位。
to_string()
: 返回转换成的字符串表达。to_ulong()
: 返回转换成的unsigned long
表达(long
在 NT 及 32 位 POSIX 系统下与int
一样,在 64 位 POSIX 下与long long
一样)。to_ullong()
:C++11,返回转换成的unsigned long long
表达。
一些文档中没有的成员函数:
_Find_first()
: 返回bitset
第一个true
的下标,若没有true
则返回bitset
的大小。_Find_next(pos)
: 返回pos
后面(下标严格大于pos
的位置)第一个true
的下标,若pos
后面没有true
则返回bitset
的大小。
应用
「LibreOJ β Round #2」贪心只能过样例
这题可以用 dp 做,转移方程很简单:
\(f(i,j)\) 表示前 \(i\) 个数的平方和能否为 \(j\),那么 \(f(i,j)=\bigvee\limits_{k=a}^bf(i-1,j-k^2)\)(或起来)。
但如果直接做的话是 \(O(n^5)\) 的,(看起来)过不了。
发现可以用 bitset
优化,左移再或起来就好了:std::bitset
然后发现……被加了几个剪枝的暴力艹了:加了几个剪枝的暴力
然而,可以手写 bitset
(只需要支持左移后或起来这一种操作)压 \(64\) 位(unsigned long long
)来艹掉暴力:手写 bitset
CF1097F Alex and a TV Show
题意
给你 \(n\) 个可重集,四种操作:
- 把某个可重集设为一个数。
- 把某个可重集设为另外两个可重集加起来。
- 把某个可重集设为从另外两个可重集中各选一个数的 \(\gcd\)。即:\(A=\{\gcd(x,y)|x\in B,y\in C\}\)。
- 询问某个可重集中某个数的个数,在模 2 意义下。
可重集个数 \(10^5\),操作个数 \(10^6\),值域 \(7000\)。
做法
看到「在模 \(2\) 意义下」,可以想到用 bitset
维护每个可重集。
这样的话,操作 \(1\) 直接设,操作 \(2\) 就是异或(因为模 \(2\)),操作 \(4\) 就是直接查,但 .. 操作 \(3\) 怎么办?
我们可以尝试维护每个可重集的所有约数构成的可重集,这样的话,操作 \(3\) 就是直接按位与。
我们可以把值域内每个数的约数构成的 bitset
预处理出来,这样操作 \(1\) 就解决了。操作 \(2\) 仍然是异或。
现在的问题是,如何通过一个可重集的约数构成的可重集得到该可重集中某个数的个数。
令原可重集为 \(A\),其约数构成的可重集为 \(A'\),我们要求 \(A\) 中 \(x\) 的个数,用 莫比乌斯反演 推一推:
由于是模 \(2\) 意义下,\(-1\) 和 \(1\) 是一样的,只用看 \(\frac d x\) 有没有平方因子即可。所以,可以对值域内每个数预处理出其倍数中除以它不含平方因子的位置构成的 bitset
,求答案的时候先按位与再 count()
就好了。
这样的话,单次询问复杂度就是 \(O(\frac v w)\)(\(v=7000,\,w=32\))。
至于预处理的部分,\(O(v\sqrt v)\) 或者 \(O(v^2)\) 预处理比较简单,\(\log\) 预处理就如下面代码所示,复杂度为调和级数,所以是 \(O(v\log v)\)。
参考代码
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
|
与埃氏筛结合
由于 bitset
快速的连续读写效率,使得它非常适合用于与埃氏筛结合打质数表。
使用的方式也很简单,只需要将埃氏筛中的布尔数组替换成 bitset
即可。
速度测试
以下代码均使用 g++-4.8 code.cpp -O2
命令行编译,CPU 使用 Intel i5-8259U 进行测试。测试结果取十次平均值。
算法 | \(5 \times 10^7\) | \(10^8\) | \(5 \times 10^8\) |
---|---|---|---|
埃氏筛 + 布尔数组 | 386ms | 773ms | 4.41s |
欧拉筛 + 布尔数组 | 257ms | 521ms | 2.70s |
埃氏筛 +bitset |
219ms | 492ms | 2.66s |
欧拉筛 +bitset |
332ms | 661ms | 3.21s |
从测试结果中可知,时间复杂度 \(O(n \log \log n)\) 的埃氏筛在使用 bitset
优化后速度甚至超过时间复杂度 \(O(n)\) 的欧拉筛,而欧拉筛在使用 bitset
后会出现「负优化」的情况。
参考代码
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
与树分块结合
bitset
与树分块结合可以解决一类求树上多条路径信息并的问题,详见 数据结构/树分块。
与莫队结合
详见 杂项/莫队配合 bitset。
计算高维偏序
详见 FHR 课件。
本页面的全部内容在 小熊老师 - 莆田青少年编程俱乐部 0594codes.cn 协议之条款下提供,附加条款亦可能应用