C++ 中的 bitset
C++ 中的 \(\textsf{bitset}\) 是能够存储 \(01\) 的容器,这一点看似与布尔(bool)数组很像。而一个布尔类型将会占用 \(1\) 字节的空间,相对于 \(\textsf{bitset}\) 来讲 \(1\) 字节的空间将可以存储 \(8\) 位的 \(01\)。
另外,\(\text{bitset}\) 的原理,是通过几个 unsigned long
做到压位的。
使用 \(\text{bitset}\)
和一般的 STL 一样,需要一个特定的头文件进行引用,但是它并不属于 STL 的哪一类。
#include <bitset>
\(\text{bitset}\) 的相关使用
1、定义
bitset <10010> BS; //10010 作为bitset的大小,下标从0开始算起,BS中都为0
bitset <10010> BS(val); //同样是 10010 位的bitset,而bitset中以及有了 val 的二进制数
// 例如,bitset <4> BS(3) 中BS的值为 0011
const string str = "101"; // str 作为一个【字符串常量】是一个01串!
bitset <10010> BS(str); // 例如,str = "101", bitset <4> BS(str) 中BS的值为 0101
bitset <10010> BS(string("101"));// 或者也可以这样写
对于整数来说,当 \(\textsf{bitset}\) 的大小小于 \(\textsf{val}\) 的值得时候,会只存入 \(\textsf{val}\) 的后面的二进制数,就像是 \(9(2) = 1001\), 而 bitset <3> BS
中的元素仅会是 \(001\)。
对与字符串来说,恰恰相反:const string str = "110101";
bitset <6> BS; // BS的值为 110101
bitset <5> BS; // BS的值为 11010
bitset <4> BS; // BS的值为 1101 ...
它是从前往后存的。
还有,bitset <10010> BS(0)
和 bitset <10010> BS
,均可使初始化为 \(0\)。
2、运算
- \(\textsf{bitset}\) 能与数组一样将每个元素进行调用,例如:当
BS
的值为 \(010110\) 时,BS[0] = 0, BS[1] = 1, BS[2] = 1, BS[3] = 0, BS[4] = 1, BS[5] = 0}
- \(\textsf{bitset}\) 可以用于左移右移、与、或、异或、非等(
&、|、^、~、&=、|=、^=、>>、<<、>>=、<<=
)的各种位运算操作,但是两个对象仅能为 \(\textsf{bitset}\)。
若两个 \(\textsf{bitset}\) 的大小不一样,那么这两个 \(\textsf{bitset}\) 是无法做到位运算的。
对于左移右移超过了其最大位,也只会保留最后的没有超过的位数。
-
\(\textsf{bitset}\) 可以用
==、!=
与其他变量进行比较,返回false
/true
。(与实数比较时,会默认将实数当做整数比较) -
\(\textsf{bitset}\) 流运算符,这意味着你可以通过
cin
/cout
进行输入输出。
3、函数
使用 | 作用 |
---|---|
BS.count() |
返回 true 的数量 |
BS.any() |
若存在某一位是 true 则返回 true ,否则返回 false |
BS.none() |
若所有位都是 false 则返回 true ,否则返回 false |
BS.all() |
若所有位都是 true 则返回 true ,否则返回 false |
BS.size() |
返回 bitset 的大小 |
BS._Find_first() |
返回 bitset 第一个 true 的下标,若没有 true 则返回 bitset 的大小 |
BS._Find_next(pos) |
返回 pos 后面(严格)第一个 true 的下标,若 pos 后面没有 true 则返回 bitset 的大小 |
使用 | 作用 |
---|---|
BS.set() |
将整个 bitset 设置成 true |
BS.set(pos,true/false) |
将某一位设置成 true/false |
BS.reset() |
将整个 bitset 设置成 false |
BS.reset(pos) |
将某一位设置成 false 。相当于 set(pos, false) |
BS.flip() |
翻转每一位,相当于异或一个全是 \(1\) 的 bitset |
BS.flip(pos) |
翻转某一位 |
使用 | 作用 |
---|---|
BS.to_string() |
返回转换成的字符串表达 |
BS.to_ulong() |
返回转换成的 unsigned long 表达 |
BS.to_ullong() |
返回转换成的 unsigned long long 表达 |
例题
题目:[Ynoi2017] 由乃的玉米田 、 P3674 小清新人渣的本愿
这个题的做法是这样的:
使用莫队来解决本题。
首先,暴力维护 \(x\) 是肯定行不通的。那么,来考虑每个操作:
-
对于减法操作,\(a-b=x\),即 \(a=b+x\)。其实可以转化为判断存在的问题,即枚举 \(b\),再判断 \(b+x\) 是否存在即可。
这个过程相当于将check[]
数组集体向右平移 \(x\),可以考虑维护一个值域的bitset
,做s & (s << x)
的操作,判断是否有 \(b\) 与 \(b+x\) 同时存在。 -
对于加法操作,\(a+b=x\),考虑转化为减法的操作。
令两次同时减去常数 \(V\)(这里 \(V\) 设为值域),得到 \(a-(V-b)=x-V\),即 \(a=x-V+(V-b)\),这里再维护一个bitest
的第 \(i\) 位维护 \(V-i\) 是否出现过。由于这里 \(V\geq x\), 所以做s1 & (s2 >> (V - x))
操作即可。
加减法的操作均用到了 bitset
的操作所以这部分的复杂度为 \(O(\frac{V}{w})\) 的。
- 对于乘法操作,无法用
bitset
类似与加减法的维护。但可以通过 \(O(\sqrt[2]x)\) 的复杂度枚举 \(x\) 的因子,判断。 - 对于除法操作,
- 如果用类似与乘法操作的思路枚举 \(x\) 的倍数 \(ax\),然后再判断 \(a\) 是否出现过,但是这样的复杂度为 \(O(\frac{V}{x})\),在 \(x\leq \sqrt[2]V\) 的时候,复杂度就炸掉了。
- 那么对于 \(x\leq \sqrt[2]V\) 的部分,做法是将这一部分的 \(x\) 全部预处理。即,对于每个 \(x\) 以及每个 \(i\),维护 \(p_x(i)\) 表示为 \(a_i\) 与 \(a_{p_x(i)}\) 的商为 \(x\) 的距离 \(i\) 最近的下标 \(j\),且 \(j\leq i\)。
答案即是在 \([l,r]\) 中是否存在一个 \(i\) 使得 \(p_x(i)\geq l\),又因为当 \(i<l\) 是的 \(p_x(i)\) 必然小于 \(l\),对 \(p_x(i)\) 做一个前缀最大值即可,复杂度为 \(O(n\sqrt[2]V)\)。
参考代码
#include <bits/stdc++.h>
const int N = 1e5;
const int V = pow(1e5, 0.5);
struct qry {
int l, r, x, id, opt;
qry() {}
qry(int il, int ir, int iid, int ix = 0, int ip = 0) :
l(il), r(ir), x(ix), id(iid), opt(ip) {}
} q[N + 10];
std::vector<qry> g[V + 10];
std::bitset<N + 10> s1, s2;
int cnt[N + 10], a[N + 10], p[N + 10], lst[N + 1], n, m, m1;
bool ans[N + 10];
void InitQuery()
{
for (int i = 1, opt, l, r, x; i <= m; i ++ ) {
std::cin >> opt >> l >> r >> x;
if (opt == 4 && x <= V) g[x].push_back(qry(l, r, i));
else q[++ m1] = qry(l, r, i, x, opt);
}
std::sort(q + 1, q + m1 + 1, [k = n / sqrt(m1)](const qry&x, const qry&y) -> bool {
int lx = x.l / k, ly = y.l / k;
return lx == ly ? lx & 1 ? x.r > y.r : x.r < y.r : lx < ly;
});
return ;
}
void Add(int x) {if (! (cnt[x] ++ )) s1[x] = s2[N - x] = true;}
void Del(int x) {if (! ( -- cnt[x])) s1[x] = s2[N - x] = false;}
int main()
{
std::cin >> n >> m;
for (int i = 1; i <= n; i ++ ) {
std::cin >> a[i];
}
InitQuery();
for (int i = 1, l = 1, r = 0; i <= m1; i ++ ) {
while (l > q[i].l) Add(a[ -- l]);
while (r < q[i].r) Add(a[ ++ r]);
while (l < q[i].l) Del(a[l ++ ]);
while (r > q[i].r) Del(a[r -- ]);
int x = q[i].x;
switch (q[i].opt) {
case 1 : ans[q[i].id] = (s1 & (s1 << x)).any(); break;
case 2 : ans[q[i].id] = (s1 & (s2 >> (N - x))).any(); break;
case 3 :
for (int j = 1; j * j <= x; j ++ ) {
if (x % j) continue;
if (j == (x / j) && s1[j] && cnt[j] > 1) {ans[q[i].id] = true; break;}
else if (s1[j] && s1[x / j]) {ans[q[i].id] = true; break;}
}
break;
case 4 :
for (int j = 1; j * x <= N; j ++ ) {
if (s1[j * x] && s1[j]) {ans[q[i].id] = true; break;}
}
break;
}
}
for (int x = 1; x <= V; x ++ ) {
if (g[x].empty()) continue;
memset(p, 0, sizeof p);
memset(lst, 0, sizeof lst);
for (int i = 1; i <= n; i ++ ) {
p[i] = p[i - 1];
lst[a[i]] = i;
if (a[i] % x == 0) p[i] = std::max(p[i], lst[a[i] / x]);
if (a[i] * x <= N) p[i] = std::max(p[i], lst[a[i] * x]);
}
for (qry tmp : g[x]) ans[tmp.id] = tmp.l <= p[tmp.r];
}
for (int i = 1; i <= m; i ++ ) puts(ans[i] ? "yuno" : "yumi");
return 0;
}