bitset作为C++一个非常好用的STL,在一些题目中巧妙地使用会产生非常不错的效果。今天扶苏来分享一点bitset的基础语法和应用

本文同步发布于个人其他博客,同时作为P3674题解发布。

本文感谢@burnside和@ddosvoid神仙帮助审稿。

注意:以下内容均按照C++98语法书写,可以在C++98下编译通过。

bitset是一个01串,每一位是占一个字节,可以进行单点0/1修改,左移右移以及按位运算操作。一个非常好用的用法是统计某个数是否出现过,类似一个桶。同时两个bitset取或可以在优秀的常数下获得两个集合是否有重复元素的信息。

声明与初始化

使用bitset需要引用头文件。

其声明方法为

std::bitset<N>s;

其中N为s的长度。

对于s的初始化,开在全局默认为全0。如果开在局部可以使用下面的reset函数置零。

同时bitset资瓷以下赋值姿势:可以使用一个同长度的std::string型01串对其进行赋值,bitset内部与string对应位相同:

// The default is that N is 8.
std::bitset<N> s (std::string("00110101"));

需要注意的是集合内部的下标是从右向左单调递增的,即字符串的第一位代表set的第7位(从0编号)。上面的赋值方法对应s的值如下表:

修改与运算

bitset中每一个元素可以通过下标的方式访问。一个长度为\(N\)的bitset下标编号为\([0,N)\)。

进行单点修改时,直接访问位置然后赋值即可:

s[pos] = x;

单点修改的复杂度为\(O(1)\)。

左移右移的写法与整形类似,移动后返回一个bitset,例如左移\(x\)位:

std::bitset<N> k = s << x;

同时资瓷<<=和>>=操作:

s <<= x;

如果将字长放在RAM模型中,约定计算机字长为\(w\)的话,一次左右移的复杂度为\(O(\frac{N}{w})\)。其中在32位系统上,\(w~=~32\),64位系统上\(w~=~64\)。由于bitset的绝大部分操作的复杂度都为\(O(\frac{N}{w})\),以下内容中,若不特殊说明,则默认操作的复杂度同上。

如果仅仅考察运算量同输入规模的增长关系或不将字长纳入RAM模型,则可认为一次操作的复杂度为\(O(N)\),常数为\(\frac{1}{32}\)或\(\frac{1}{64}\),这两种说法在文献上都有出现,在OI中为了方便计算运算量,一般使用第一种表达方法。以下内容一律使用\(O(\frac{N}{w})\)的表示法。

同时bitset资瓷二元按位运算,即资瓷与、或、异或三个运算符:返回值为一个bitset。结果为运算符两侧的bitset按位运算的结果。

std::bitset<N> k = s & s0;

输入输出与转换

bitset重载了<>输入输出流,可以使用std::cin和std::cout来读入和输出一个bitset的所有元素。

当读入的长度小于bitset的位数时,会从第0位开始赋值直到字符串结束。当读入长度大于bitset的位数时,在扶苏的MinGW编译器上会截取前【位数】个进行赋值。未尝试过在其他环境下的结果。

注意,输出时bitset是反着输出的,即第0位是从右向左数第1个:

std::cin >> s;                          // 1101
std::cout << s << std::endl;            // 000001101

从上述例子中可以看出,输入字符串的第【长度】位是集合的第1位。注意事项和使用std::string进行初始化的一致。

bitset提供两个转换函数,可以转换为std::string型,unsigned long int型(即unsigned int)。函数名为别为to_string()和to_ulong();

C++11标准下可以转换为unsigned long long int型,函数名为to_llong()

其中转换成std::string型会转换成01串,其他两个类型会按照二进制位转换成十进制数字。

当bitset大小大于32位时,转换成unsigned long int会导致RE,大于64位时,转换成unsigned long long int会产生RE:

// The default is that N is 8.
s.reset();
s[2] = true;
unsigned int s1 = s.to_ulong();
std::cout << s1 << std::endl;                   //4
unsigned long long int s2 = s.to_ullong();      //C++11
std::cout << s2 << std::endl;                   //4
std::string ss = s.to_string();
std::cout << ss << std::endl;                   //00000100

成员函数

reset

bitset的清空操作为reset。将集合内元素全部置零:

s.reset();

set

set有两种用法,第一种是直接调用set不带参数,会将bitset内所有元素置1,另一种是set后加两个参数,分别是pos和val,意为将bitset中第pos个元素的值置为v。当v为true时可以省略不写。

s.reset();
s.set()             //11111111
s.set(3,false)      //11110111
s.set(3)            //11111111

test

test有一个参数pos,返回一个bitset内第pos位的值。

s.reset();
s.set(7);
int k = s.test(7);      // k is true
k = s.test(6);          // k is false

any

bitset有一个成员函数为any,返回一个布尔量。若bitset内部存在一位的值为1,则返回true,否则返回false:

s.clear();
bool k = s.any();           //k is false
s[1] = true;
k = s.any()                 //k is true

复杂度同上。按照不同编译器版本的实现方法,.any()的常数甚至有可能小于理论值。

none

与any相对,返回一个布尔量,不存在任何一个位置的值为1则返回true,否则返回false。

s.clear();
bool k = s.none();          //k is true
s[1] = true;
k = s.none()                //k is false

count

count返回一个bitset内1的个数,是一个无符号整形:

s.reset();
int k = s.count();              // k is 0
s[1] = true;
k = s.count();                  // k is 1

需要注意的是目前扶苏已知的资料中,count的复杂度也是\(O(\frac{N}{w})\)。

当然想知道0的个数可以用总长度减去count喽

flip

flip函数类似于按位取反,它的两个声明如下:

bitset& flip();
bitset& flip (size_t pos);

当调用s.flip()且括号内无参数时,会将集合内所有元素取反(0变1,1变0)

当调用s.flip(x)时,会将第x位取反(从0编号)

s.reset();
s[1] = true;    //s is "01000000"
s.flip();       //s is "10111111"
s.flip(1);      //s is "11111111"

手写一个bitset

注意,以下手写bitset内容因为时间紧迫,部分函数未进行单元测试,经测试仅仅A掉P3674 小清新人渣的本愿。如果您发现代码错误请在评论区发表评论或者私信联系@一扶苏一。在此表示感谢qwq

bitset的过度封装导致bitset的一些操作不能实现,比如两个二进制数求lowbit。怎么办呢,我们可以手写一个bitset!

一个bitset的显然可以用一堆unsigned long long拼成的数组实现,一些简单的位运算和成员函数实现十分简单,会在下方直接给出代码,现在我们考虑左移和右移怎么实现。

以下不妨设bitset字长BitNum为64。

不妨设进行左移,如果左移位数v小于64,那么可以单个数字直接移动,我们考虑两个数字之间移动的部分,就是左侧数字的最高v位成为了右侧数字的最低v位,于是我们记录一下这最高的v位就可以迭代了。

考虑左移位数大于64的情况,我们发现这种情况等价于先整个数组左移\(\frac{v}{BitNum}\)位置,然后做模意义下的移动即可。

然后考虑仅仅这么做的话,因为我们的bitset不一定是严格【位数】长度的,可能一个数字左移以后再右移会爆炸。比如一个长度为 \(3\) 的二进制串 \(s~=~111\) ,对 \(s\) 先左移一位(110)再右移一位应该得到011,但是因为我们的单个字符一存就是64位,这么做会让答案得到111。具体的解决方法是每次操作结束以后把最高位应该是 \(0\) 的位置全部截掉。

具体位移部分代码如下

注意代码中_Ary的下标越大代表的二进制位越小。即_Size-1的下标代表二进制串的 \(0~\sim~63\) 位,以此类推

    Bitset() {                                          //constructed function of std::string is left out because i dont know how to implement itmemset(_Ary, 0, sizeof _Ary);int _firstsize = _len % _BitNum;for (rg int i = 0; i < _firstsize; ++i) _upceil |= 1ull << i;if (!_firstsize) _upceil = _INF;}void reset() {*this = Bitset();}//operatorsvoid rtmve(const int &_v) {for (rg int i = _Size - 1; i >= _v; --i) this->_Ary[i] = this->_Ary[i - _v];for (rg int i = _v - 1; ~i; --i) this->_Ary[i] = 0;}void lftmve(const int &_v) {for (rg int i = 0; (i + _v) < _Size; ++i) this->_Ary[i] = this->_Ary[i + _v];for (rg int i = _Size - _v; i < _Size; ++i) this->_Ary[i] = 0;}Bitset& operator<<=(int _v) {if (_v < 0) {*this >>= -_v;return *this;}this->lftmve(_v / _BitNum);_v %= _BitNum;ull _tp = 0, _Pos = _BitNum - _v;for (rg int  i = 1; i <= _v; ++i) _tp |= 1ull << (_BitNum - i);ull _Lstv = 0;for (rg int i = _Size - 1; ~i; --i) {ull _Tp_Lstv = (_Ary[i] & _tp) >> _Pos;_Ary[i] <<= _v;_Ary[i] |= _Lstv;_Lstv = _Tp_Lstv;}this->_Ary[0] &= _upceil;           // cut off redundant digitreturn *this;}Bitset& operator>>=(int _v) {if (_v < 0) {*this <<= -_v;return *this;}this->rtmve(_v / _BitNum);_v %= _BitNum;ull _tp = (1ull << _v)- 1;ull _Lstv = 0, __Pos = _BitNum - _v;for (rg int i = 0; i < _Size; ++i) {ull _Tp_Lstv = (_Ary[i] & _tp) << __Pos;_Ary[i] >>= _v;_Ary[i] |= _Lstv;_Lstv = _Tp_Lstv;}this->_Ary[0] &= _upceil;            cut off redundant digitreturn *this;}

考虑set和test操作,我们需要定位这一位在数组中的位置,如果按照上面所述的方法存二进制串的话,第二进制串第v个位置应该对应的数组位置__Pos和这一个数上的二进制位置_v应该按照如下方法寻找

    inline void __GetPos(const ull &_pos, int &__Pos, int &_v) {__Pos = _Size - _pos / _BitNum - 1;_v = _pos % _BitNum;}

考虑我们如何修改一个位置的值:如果修改为true,则可以直接与1ull<<v取或,如果修改为false,则可以先修改为true,然后与1ull<<v取异或

    void set(const ull &_pos, const bool val = true) {int __Pos , _v;__GetPos(_pos,__Pos,_v);if(val) {this->_Ary[__Pos] |= (1ull << (_v));} else {this->_Ary[__Pos] |= (1ull << (_v));this->_Ary[__Pos] ^= (1ull << (_v));}}

剩下的部分就很简单辣,这里直接给出代码,这份代码中实现了大部分bitset的操作,因为我不会对单个bit取值,所以没有重载[]运算符,对集合的修改需要使用set和test函数。

#define Fusu_Bitset#ifdef Fusu_Bitset
#include <cstddef>              //size_t used
#include <cstring>#include <string>
#include <algorithm>
#endifnamespace Fusu {template <size_t _len>
struct Bitset {
#define rg register
#define ci const int
#define cl const long longtypedef long long int ll;typedef unsigned long long int ull;const static int _BitNum = 64;const static int _Size = _len / _BitNum + ((_len % _BitNum) == 0 ? 0 : 1);ull _Ary[_Size];ull _upceil;const static ull _INF = (1ull << 63) - 1 + (1ull << 63);Bitset() {                                          //constructed function of std::string is left out because i dont know how to implement itmemset(_Ary, 0, sizeof _Ary);int _firstsize = _len % _BitNum;for (rg int i = 0; i < _firstsize; ++i) _upceil |= 1ull << i;if (!_firstsize) _upceil = _INF;}void reset() {*this = Bitset();}//operatorsvoid rtmve(const int &_v) {for (rg int i = _Size - 1; i >= _v; --i) this->_Ary[i] = this->_Ary[i - _v];for (rg int i = _v - 1; ~i; --i) this->_Ary[i] = 0;}void lftmve(const int &_v) {for (rg int i = 0; (i + _v) < _Size; ++i) this->_Ary[i] = this->_Ary[i + _v];for (rg int i = _Size - _v; i < _Size; ++i) this->_Ary[i] = 0;}Bitset& operator<<=(int _v) {if (_v < 0) {*this >>= -_v;return *this;}this->lftmve(_v / _BitNum);_v %= _BitNum;ull _tp = 0, _Pos = _BitNum - _v;for (rg int  i = 1; i <= _v; ++i) _tp |= 1ull << (_BitNum - i);ull _Lstv = 0;for (rg int i = _Size - 1; ~i; --i) {ull _Tp_Lstv = (_Ary[i] & _tp) >> _Pos;_Ary[i] <<= _v;_Ary[i] |= _Lstv;_Lstv = _Tp_Lstv;}this->_Ary[0] &= _upceil;return *this;}Bitset& operator>>=(int _v) {if (_v < 0) {*this <<= -_v;return *this;}this->rtmve(_v / _BitNum);_v %= _BitNum;ull _tp = (1ull << _v)- 1;ull _Lstv = 0, __Pos = _BitNum - _v;for (rg int i = 0; i < _Size; ++i) {ull _Tp_Lstv = (_Ary[i] & _tp) << __Pos;_Ary[i] >>= _v;_Ary[i] |= _Lstv;_Lstv = _Tp_Lstv;}this->_Ary[0] &= _upceil;return *this;}Bitset operator&(const Bitset &_others) const {Bitset _ret;for (rg int i = _Size - 1; ~i; --i) {_ret._Ary[i] = this->_Ary[i] & _others._Ary[i];}return _ret;}Bitset operator|(const Bitset &_others) const {Bitset _ret;for (rg int i = _Size - 1; ~i; --i) {_ret._Ary[i] = this->_Ary[i] | _others._Ary[i];}return _ret;}Bitset operator^(const Bitset &_others) const {Bitset _ret;for (rg int i = _Size - 1; ~i; --i) {_ret._Ary[i] = this->_Ary[i] ^ _others._Ary[i];}return _ret;}Bitset operator~() const {Bitset _ret;for (rg int i = _Size - 1; ~i; --i) {_ret._Ary[i] = ~this->_Ary[i];}return _ret;}Bitset operator<<(const int &_v) const {Bitset x = *this;x <<= _v;return x;}Bitset operator>>(const int &_v) const {Bitset x = *this;x >>= _v;return x;}//member functionsinline void __GetPos(const ull &_pos, int &__Pos, int &_v) {__Pos = _Size - _pos / _BitNum - 1;_v = _pos % _BitNum;}void set() {for (rg int i = 0; i < _Size; ++i) this->_Ary[i] = _INF;}void set(const ull &_pos, const bool val = true) {int __Pos , _v;__GetPos(_pos,__Pos,_v);if (val) {this->_Ary[__Pos] |= (1ull << (_v));} else {this->_Ary[__Pos] |= (1ull << (_v));this->_Ary[__Pos] ^= (1ull << (_v));}}int test(const ull &_pos) {int __Pos , _v;__GetPos(_pos,__Pos,_v);return this->_Ary[__Pos] & (1ull << (_v)) ? 1 : 0;}bool any() {for (rg int i = _Size - 1; ~i; --i) if (this->_Ary[i]) return true;return false;}bool none() {return !this->any();}ull conut() {ull _cnt = 0;for (rg int i = _Size - 1; ~i; --i) _cnt += __builtin_popcount(this->_Ary[i]);          /**if u cant used double_underlined functions, *u can set a val to maintain the num of true*and change it in other operators which would change the num of true*/return _cnt;}void flip() {*(this) = ~(*this);}void flip(const ull &_pos) {if(this->test(_pos)) this->set(_pos, false);else this->set(_pos, true);}//changing functionsstd::string to_string() {std::string _ret;_ret.clear();for (rg int i = 0; i < _Size; ++i) {for (rg int j = _BitNum - 1; ~j; --j) _ret += (this->_Ary[i] & (1ull << j)) ? '1' : '0';}return _ret;}unsigned int to_ulong() {return this->_Ary[_Size - 1];}
};
}   //namespace

Samples

P1537弹珠

这是非常显然的一个布尔背包问题,因为数据规模比较小,可以直接把多重背包拆分成01背包做。

考虑朴素的做法显然是设\(f_{i,j}\)为前\(i\)个中,第\(j\)的价值能否拼出来,转移方程显然:

\[f_{i,j}~|=~f_{i-1,j-w_i}\]

复杂度\(O(n\sum~w_i)\),考虑可以滚动掉第一维,然后考察转移,实质上是将原数组的01串左移了\(w_i\)位与原串取或。

于是如果设\(f\)是一个bitset,则转移可以写成

\[f_j~|=f_{j}~<<~w_i\]

复杂度\(O(n~\sum~w_i~\times~\frac{1}{w})\),实际效率十分优秀。

使用bitset优化01bool背包的转移是bitset一个比较常见的用法。

#include <cstdio>
#include <cstring>
#include <bitset>
#include <iostream>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
#define rg register
#define ci const int
#define cl const long longtypedef long long int ll;namespace IPT {const int L = 1000000;char buf[L], *front=buf, *end=buf;char GetChar() {if (front == end) {end = buf + fread(front = buf, 1, L, stdin);if (front == end) return -1;}return *(front++);}
}template <typename T>
inline void qr(T &x) {rg char ch = IPT::GetChar(), lst = ' ';while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();if (lst == '-') x = -x;
}template <typename T>
inline void ReadDb(T &x) {rg char ch = IPT::GetChar(), lst = ' ';while ((ch > '9') || (ch < '0')) lst = ch, ch = IPT::GetChar();while ((ch >= '0') && (ch <= '9')) x = x * 10 + (ch ^ 48), ch = IPT::GetChar();if (ch == '.') {ch = IPT::GetChar();double base = 1;while ((ch >= '0') && (ch <= '9')) x += (ch ^ 48) * ((base *= 0.1)), ch = IPT::GetChar();}if (lst == '-') x = -x;
}namespace OPT {char buf[120];
}template <typename T>
inline void qw(T x, const char aft, const bool pt) {if (x < 0) {x = -x, putchar('-');}rg int top=0;do {OPT::buf[++top] = x % 10 + '0';} while (x /= 10);while (top) putchar(OPT::buf[top--]);if (pt) putchar(aft);
}const int maxn = 7;
const int maxt = 20010;int MU[maxn], kase;
std::bitset<maxt>frog;void clear();int main() {freopen("1.in", "r", stdin);while (true) {clear();for (rg int i = 1; i < 7; ++i) qr(MU[i]);int sum = 0;for (rg int i = 1; i < 7; ++i) sum += MU[i] * i;if (!sum) return 0;printf("Collection #%d:\n", ++kase);if (sum & 1) {puts("Can't be divided.\n");continue;}frog.set(0);for (rg int i = 1; i < 7; ++i) {for (rg int j = 1; j <= MU[i]; ++j)frog |= (frog <<  i);}if (frog.test(sum >> 1)) puts("Can be divided.\n");else puts("Can't be divided.\n");}return 0;
}void clear() {frog.reset();memset(MU, 0, sizeof MU);
}

P5020 货币系统

既然bitset可以优化01背包,那么可不可以优化完全背包呢?事实上完全是可以的,比如这道题。

本题是一个bool完全背包的递推,不会做请右转题解区。考虑这题布尔背包的朴素转移\(O(n~\times~\max\{a_i\})\)的,但是可以通过bitset来降低运算量

具体的,我们发现一个位置\(x\)能被表示出来当且仅当\(f_{x-k~\times~a_i}~= true\),其中\(f\)为布尔数组。即\(f_x~|=~f_{x-k~\times~a_i}\)。我们用bitset来作为这个布尔数组,于是发现上式等价于\(f~|=~f~<<(k~\times~a_i)\)。枚举一下\(k\)即可转移。

接着发现当\(k~=~3\)时的结果等价于\(k~=~1\)再\(k~=~2\)的结果叠加,于是\(3\)是不需要枚举的。\(k~=~5\)时的结果与\(k~=~1\)再\(k~=~4\)的的结果是相同的,也不需要枚举。同理可以发现非二的幂次的\(k\)都不需要枚举,于是可以只枚举2的幂次。

考虑复杂度,一共枚举\(n\)个值,每次枚举从\(a_i\)逐次乘二到\(\max\{a_i\}\),一共乘\(\log (\max\{a_i\})\)次。一次bitset的操作复杂度为\(O(\frac{\max\{a_i\}}{w})\),于是总复杂度\(O(n~\max\{a_i\}~\log (\max\{a_i\})~\times~\frac{1}{w})\)。实际运算量不增反降。

Code

#include <cstdio>
#include <cstring>
#include <bitset>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
#define rg register
#define ci const int
#define cl const long longtypedef long long int ll;namespace IPT {const int L = 1000000;char buf[L], *front=buf, *end=buf;char GetChar() {if (front == end) {end = buf + fread(front = buf, 1, L, stdin);if (front == end) return -1;}return *(front++);}
}template <typename T>
inline void qr(T &x) {rg char ch = IPT::GetChar(), lst = ' ';while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();if (lst == '-') x = -x;
}namespace OPT {char buf[120];
}template <typename T>
inline void qw(T x, const char aft, const bool pt) {if (x < 0) {x = -x, putchar('-');}rg int top=0;do {OPT::buf[++top] = x % 10 + '0';} while (x /= 10);while (top) putchar(OPT::buf[top--]);if (pt) putchar(aft);
}const int maxn = 110;
const int maxt = 25010;int t,n;
int MU[maxn];
std::bitset<maxt>s;void clear();int main() {freopen("1.in", "r", stdin);qr(t);while(t--) {clear();qr(n);for (rg int i = 1; i <= n; ++i) qr(MU[i]);std::sort(MU + 1, MU + 1 + n);s[0] = true;rg int ans = 0;for (rg int i = 1; i <= n; ++i) if(!s[MU[i]]) {++ans;int x = MU[i];while(x <= MU[n]) {s |= s << x;x <<= 1;}}qw(ans, '\n' ,true);}return 0;
}void clear() {n = 0;memset(MU, 0, sizeof MU);s.reset();
}

P3674 小清新人渣的本愿

这是一个标准的bieset题目。

不带修改,资瓷离线,而且还是dllxl大爷的题,于是考虑使用莫队解决。

考虑在查询时如何维护答案:

如果当前序列中

\[\exists~a,b~~,~~s.t.~a~-~b~=~x\]

移项得

\(a~=~x~+~b\)

即这个序列中同时出现了一个数\(y\)和\(y~+~x\)。

即\(s[y]~=~true\)且\(s[y+x]~=~true\)

这等价于 \(s~|~(s<<x)\) 存在一个1。于是用bitset维护出现的数字即可。

考虑加法:

\[\exists~a,b~~,~~s.t.~a~+~b~=~x\]

我们令\(b'~=~n~-~b\),则

\[a~+~b~=x\]

\[\Rightarrow~a~-~b'~=~a~-~(n~-~b)~=~a~+~b~-~n~=~x~-~n\]

移项得

于是按照减法的方式,再维护一个bitset表示n-y是否出现即可,

对于乘法的情况,因为枚举一个数的因数只需要\(O(\sqrt{n})\),于是直接暴力枚举因数即可。

于是查询的复杂度\(O(\frac{nm}{w})\),可以通过本题。

Code

#prag\
ma GCC optimize(3)
#include <cmath>
#include <cstdio>
#include <bitset>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
#define rg register
#define ci const int
#define cl const long longtypedef long long int ll;namespace IPT {const int L = 1000000;char buf[L], *front=buf, *end=buf;char GetChar() {if (front == end) {end = buf + fread(front = buf, 1, L, stdin);if (front == end) return -1;}return *(front++);}
}template <typename T>
inline void qr(T &x) {rg char ch = IPT::GetChar(), lst = ' ';while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();if (lst == '-') x = -x;
}template <typename T>
inline void ReadDb(T &x) {rg char ch = IPT::GetChar(), lst = ' ';while ((ch > '9') || (ch < '0')) lst = ch, ch = IPT::GetChar();while ((ch >= '0') && (ch <= '9')) x = x * 10 + (ch ^ 48), ch = IPT::GetChar();if (ch == '.') {ch = IPT::GetChar();double base = 1;while ((ch >= '0') && (ch <= '9')) x += (ch ^ 48) * ((base *= 0.1)), ch = IPT::GetChar();}if (lst == '-') x = -x;
}namespace OPT {char buf[120];
}template <typename T>
inline void qw(T x, const char aft, const bool pt) {if (x < 0) {x = -x, putchar('-');}rg int top=0;do {OPT::buf[++top] = x % 10 + '0';} while (x /= 10);while (top) putchar(OPT::buf[top--]);if (pt) putchar(aft);
}const int maxn = 100010;std::bitset<maxn>s1,s2;int n, m;
int MU[maxn], belong[maxn], oc[maxn];struct Ask {int opt, l, r, v, id;bool ans;inline bool operator<(const Ask &_others) const {if (belong[this->l] != belong[_others.l]) return this->l < _others.l;if (belong[this->l] & 1) return this->r < _others.r;return this->r > _others.r;}
};
Ask ask[maxn];
inline bool cmp(const Ask &_a,const Ask &_b) {return _a.id < _b.id;
}void add(int);
void dlt(int);int main() {freopen("1.in", "r", stdin);qr(n); qr(m);for (rg int i = 1; i <= n; ++i) qr(MU[i]);for (rg int i = 1; i <= m; ++i) {Ask &now = ask[i];qr(now.opt); qr(now.l); qr(now.r); qr(now.v); now.id = i;}for (rg int i = 1, sn = sqrt(n); i <= n; ++i) belong[i] = i / sn;std::sort(ask + 1, ask + 1 + m);int prel = ask[1].l, prer = prel - 1;for (rg int  i = 1; i <= m; ++i) {int l = ask[i].l, r = ask[i].r;while (prel < l) dlt(prel++);while (prel > l) add(--prel);while (prer < r) add(++prer);while (prer > r) dlt(prer--);int op = ask[i].opt, x = ask[i].v;if (op == 1) {ask[i].ans = (s1 & (s1 << x)).any();} else if (op == 2) {ask[i].ans = (s1 & (s2 >> (n - x))).any();} else {for (rg int j = 1; (j * j) <= x; ++j) if(!(x % j)) {if(s1[j] && s1[x / j]) {ask[i].ans = true; break;}}}}std::sort(ask + 1, ask + 1 + m, cmp);for (rg int i = 1; i <= m; ++i) puts(ask[i].ans ? "hana" : "bi");return 0;
}inline void add(int x) {x = MU[x];if (!(oc[x]++)) s1[x] = s2[n - x] = true;
}inline void dlt(int x) {x = MU[x];if (!(--oc[x])) s1[x] = s2[n - x] = false;
}

Summary

bitset是C++中十分灵活的一个STL,在许多题目中可以起到优化常数乃至复杂度、降低代码难度的效果。你学会了嘛~

转载于:https://www.cnblogs.com/yifusuyi/p/10072729.html

扶苏的bitset浅谈相关推荐

  1. java bitset用途_浅谈Java BitSet使用场景和代码示例

    搜索热词 @H_502_0@一.什么是BitSet? @H_502_0@ 注:以下内容来自JDK API: @H_502_0@ BitSet类实现了一个按需增长的位向量.位Set的每一个组件都有一个b ...

  2. 虚拟化构建二分图(BZOJ2080 题解+浅谈几道双栈排序思想的题)

    虚拟化构建二分图 ------BZOJ2080 题解+浅谈几道双栈排序思想的题 本题的题解在最下面↓↓↓ 不得不说,第一次接触类似于双栈排序的这种题,是在BZOJ的五月月赛上. [BZOJ4881][ ...

  3. 【读书笔记】舆情分析浅谈:读《论新媒体发展背景下网络舆情的发展及治理》有感

    舆情分析浅谈 读<论新媒体发展背景下网络舆情的发展及治理>有感 最近读了一些有关"舆情"的论文,其中印象比较深的有一篇<论新媒体发展背景下网络舆情的发展及治理&g ...

  4. 浅谈MySQL存储引擎-InnoDBMyISAM

    浅谈MySQL存储引擎-InnoDB&MyISAM 存储引擎在MySQL的逻辑架构中位于第三层,负责MySQL中的数据的存储和提取.MySQL存储引擎有很多,不同的存储引擎保存数据和索引的方式 ...

  5. 【大话设计模式】——浅谈设计模式基础

    初学设计模式给我最大的感受是:人类真是伟大啊!单单是设计模式的基础课程就让我感受到了强烈的生活气息. 个人感觉<大话设计模式>这本书写的真好.让貌似非常晦涩难懂的设计模式变的生活化.趣味化 ...

  6. 学校计算机机房好处,浅谈学校计算机机房维护

    浅谈学校计算机机房维护    现在的学校机房都配置了数量较多的计算机,而且机房的使用非常频繁.对于怎样维护好计算机,特别是计算机软件系统,对广大计算机教师来说是一个很重要且非常现实的问题.下面就本人在 ...

  7. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

  8. mybatis与php,浅谈mybatis中的#和$的区别

    浅谈mybatis中的#和$的区别 发布于 2016-07-30 11:14:47 | 236 次阅读 | 评论: 0 | 来源: 网友投递 MyBatis 基于Java的持久层框架MyBatis 本 ...

  9. 浅谈GCC预编译头技术

    浅谈GCC预编译头技术 文/jorge --谨以此文,悼念我等待MinGW编译时逝去的那些时间. 其 实刚开始编程的时候,我是丝毫不重视编译速度之类的问题的,原因很简单,因为那时我用BASICA.后来 ...

最新文章

  1. 40年诞生7位诺奖得主,美国贝尔实验室做对了什么?
  2. PHP的查找stripos strpos strripos strpos
  3. linux上卸载kafka,kafka安装在linux上的安装
  4. Qt实现多屏幕多分辨率自适应
  5. IntelliJ Idea 常用快捷键 超实用!
  6. Android--多选自动搜索提示
  7. oracle错误 无监听程序,oracle_无监听程序_错误
  8. 算法:翻转链表 Reverse Linked List 三种方法实现,迭代解决人类思维,递归解决机器思维 reverse node
  9. 关于高德地图自定义地图样式只显示中国的解决方案
  10. 规格说明书练习-吉林市1日游
  11. php读取json三级,php-流明从文件中读取JSON
  12. MCV模型流程图整理
  13. 筋斗云案例 - animate、transition都能实现
  14. 96Boards MIPI CSI Camera Mezzanine
  15. Ubuntu 截图工具 Flameshot
  16. 专访Mockplus用户齐嘉伟 | Mockplus满足做原型的所有需求
  17. 指针与const 深析
  18. lambda表达式和Stream
  19. js中的reduce函数详细解释
  20. 【优化调度】基于matlab遗传算法求解公交车调度排班优化问题【含Matlab源码 2212期】

热门文章

  1. 系统消息的发送和接收
  2. Linux Shell脚本检查一个目录是否存在,不存在则创建的方法
  3. 使用CAShapeLayer与UIBezierPath画动态柱状图
  4. Visio更改图形块的内边距
  5. 嵌入式应用市场四大热点及趋势
  6. 谈一谈Android适配(从蓝湖切图模糊问题说开去)
  7. 从一个NPU失效问题看Linux PM Domain Framework的实现逻辑
  8. map与sync.Map
  9. 【博学谷学习记录】超强总结,用心分享| 数据仓库
  10. 随机字符串解决大问题之腾讯网如何实现手机扫描二维码登录qq功能的