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常用于记录状态的存在性,所以可以快速维护一些关系

例题 洛谷 P3674 小清新人渣的本愿

本题给出一个区间,一个数 \(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浅谈

posted @ 2025-08-08 11:21  Hirasawayuiii  阅读(62)  评论(0)    收藏  举报