bitset介绍与应用
bitset介绍与应用
bitset是c++中的一个stl。简单来说,可以存储一个01串,并进行大部分修改、查询操作,拥有优秀的复杂度与常数。
基本操作
初始化
bitset <N> s;
即声明了一个长度为 \(N\) 的bitset,命名为s,其中全部为0
可以通过整型变量或字符串直接赋值
string s = "1010";
bitset <N> s1(str);
bitset <N> s2(6);
需要注意的是其赋值是按照二进制规则,即从低位到高位赋值的,如例子中的s1,其内部应为1010,s2应为011,不足高位均补0
修改
调用(以输出为例)
cout << s[p];
单点修改 复杂度 \(O(1)\)
s[p] = x;
左移右移 复杂度 \(O(\frac{N}{w})\),其中 \(w=64\),即计算机系统位数
s <<= x, s >>= x;
bitset <N> ss = s << x;
二进制按位运算
s &= s1, s |= s2, s ^= s3;
bitset <N> ss = s & s1;
输入/输出
直接cin/cout可以从低位到高位输入/输出bitset内的所有元素
cin >> s; // 1101
cout << s; // 000...0001101,高位补0
通过bitset内置的函数可以将其转化为unsigned int,unsigned long long或是string类型
bitset <N> s;
s[1] = 1, s[2] = 1; // 110
ui s1 = s.to_ulong();
ull s2 = s.to_ullong(); // c++11及其上
string s3 = s.to_string();
cout << s1 << endl; // 6
cout << s2 << endl; // 6
cout << s3 << endl; // 00...00110
成员函数
// 返回整型值
s.count() // 返回为1的数量
s.size() // 返回大小
// 返回布尔值
s.any() // 只要有1就为真
s.none() // 全为0为真
s.all() // 全为1为真
// 操作函数
s.reset(); // 清空
s.flip(); // 全部取反
s.flip(x); // 只将x位取反
// libstdc++中的函数
s._Find_first() // 返回第一个为1的下标(从0开始),若没有1则返回s.size()
s._Find_next(x) // 返回x后第一个1的下标,若没有1则返回s.size()
应用
维护关系
bitset常用于记录状态的存在性,所以可以快速维护一些关系
本题给出一个区间,一个数 \(x\),询问区间内是否有 \(a_i+a_j=x\) 或 \(a_i-a_j=x\) 或 \(a_i\times a_j=x\)。
因为离线,所以使用莫队
对于加减法,可以使用一个bitset代替正常莫队里的标记当前数是否存在的数组
区间处理完成后通过左右移操作即可快速得出
如:区间内有 \(2,3\) 两数,问是否有相减得 \(1\)
此时bitset内 \(s_2=s_3=1\),得到 \(s\&(s>>1)\) 中 \(s_2=1\),所以存在
加法同理,只需移项维护 \(n-a_i\) 即可判断
而对于乘法,因为题目中给出具体数值最大值 \(c\le 10^5\),所以直接采取暴力枚举因数判断
时间复杂度 \(O(m(\sqrt n+\frac{c}{w}))\)
代码实现:
for(int i = 1; i <= m; i++) {
int ql = q[i].l, qr = q[i].r, op = q[i].op, x = q[i].x, flg = 0;
while(ql < l) mov(a[--l]);
while(r < qr) mov(a[++r]);
while(l < ql) del(a[l++]);
while(qr < r) del(a[r--]);
if(op == 1) {
if((s&(s>>x)).any()) flg = 1;
}
else if(op == 2) {
if((s&(c>>(maxn-x))).any()) flg = 1;
}
else {
for(int j = 1; j <= sqrt(x); j++) {
if(x%j) continue;
if(s[j] && s[x/j]) {
flg = 1;
break;
}
}
}
ans[q[i].idx] = flg;
}
优化dp
前文提到bitset常用于记录状态的存在性等0/1量,并且bitset由于其左右移或是二进制运算都有较好的时间复杂度,所以可以优化01布尔dp
// 原dp:
for(int i = 1; i <= n; i++) {
int c = i&1;
for(int j = 1; j <= n; j++) {
f[c][j][0] = (f[c^1][j-1][1]|f[c^1][j-1][2])&((~r[j+i])&(~(r[j+i-1])));
f[c][j][1] = (f[c^1][j][0]|f[c^1][j][2])&(~r[j+i]);
f[c][j][2] = (f[c^1][j][0]|f[c^1][j][1])&(~l[j-i]);
}
}
// ----------------------------------------------------------------------------
// bitset优化后:
bitset <N> f[2][3], l, r;
for(int i = 1; i <= len; i++) {
int c = i&1;
f[c][0] = ((f[c^1][1]|f[c^1][2])<<1)&((~(r>>i))&(~(r>>(i-1))));
f[c][1] = (f[c^1][0]|f[c^1][2])&(~(r>>i));
f[c][2] = (f[c^1][1]|f[c^1][0])&(~(l<<i));
}
可以看到,原 \(O(N)\) 的内层循环被优化为了 \(O(\frac{N}{w})\) 的左右移操作,总复杂度也降为了 \(O(\frac{N^2}{w})\),从而能够通过时间限制
例题 洛谷 P1537 弹珠
本题为多重背包板子,可用二进制优化作出,而对于直接单个拆开的01背包暴力做法:
f[0] = 1;
for(int i = 1; i <= 6; i++)
for(int j = 1; j <= a[i]; j++)
for(int k = sum/2; k >= i; k--)
f[k] |= f[k-i];
设弹珠价值总和为 \(sum\),复杂度为 \(O(sum\sum a[i])\),显然不能通过此题(数据太水了还是过了)
可用bitset优化为 \(O(\frac{sum\sum a[i]}{w})\):
s.reset();
s[0] = true;
for(int i = 1; i <= 6; i++)
for(int j = 1; j <= a[i]; j++)
s |= (s<<i);
本文大量参考 oiwiki 与 扶苏的bitset浅谈