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 it
memset(_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();}
//operators void 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 digit
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; //// cut off redundant digit
return *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>
#endif namespace Fusu { template <size_t _len>
struct Bitset {
#define rg register
#define ci const int
#define cl const long long
typedef 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 it
memset(_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();}
//operators void 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 functions
inline 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 functions
std::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 long typedef 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 long typedef 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 long typedef 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,在许多题目中可以起到优化常数乃至复杂度、降低代码难度的效果。你学会了嘛~

扶苏的bitset浅谈的更多相关文章

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

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

  2. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  3. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

  4. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  5. 浅谈SQL注入风险 - 一个Login拿下Server

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...

  6. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  7. 浅谈angular2+ionic2

    浅谈angular2+ionic2   前言: 不要用angular的语法去写angular2,有人说二者就像Java和JavaScript的区别.   1. 项目所用:angular2+ionic2 ...

  8. iOS开发之浅谈MVVM的架构设计与团队协作

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  9. Linux特殊符号浅谈

    Linux特殊字符浅谈 我们经常跟键盘上面那些特殊符号比如(?.!.~...)打交道,其实在Linux有其独特的含义,大致可以分为三类:Linux特殊符号.通配符.正则表达式. Linux特殊符号又可 ...

随机推荐

  1. 11-Dockerfile构建镜像

    用 Dockerfile 创建上节的 ubuntu-with-vi,其内容则为: FROM ubuntu RUN apt-get update && apt-get install v ...

  2. No.03---Vue学习之路之模块化组织

    前两篇讲解了一下 Vuex 的基本使用方法,可是在实际项目中那么写肯定是不合理的,如果组件太多,不可能把所有组件的数据都放到一个 store.js 中的,所以就需要模块化的组织 Vuex,首先看一下 ...

  3. AsciiPic Java视频转成字符画

    AsciiPic Java视频转成字符画 github下载 https://github.com/dejavudwh/AsciiPic 运行截图 //没有做GUI 比较简陋 节省时间 main里的文件 ...

  4. Spark之spark shell

    前言:要学习spark程序开发,建议先学习spark-shell交互式学习,加深对spark程序开发的理解.spark-shell提供了一种学习API的简单方式,以及一个能够进行交互式分析数据的强大工 ...

  5. 【Linux 运维】 date的使用

    date的使用 一.常用时间格式 #年.月.日 四位年大写,其余小写 [root@localhost ~]# date +%Y #长格式显示四位数年 [root@localhost ~]# date ...

  6. JAVA学习笔记--策略设计模式与适配器模式

    一.策略设计模式 创建一个能够根据所传递对象的不同而具有不同行为的方法被称为策略设计模式:这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分.策略就是传递进去的参数对象,它包含要执行 ...

  7. zigbee,质量追溯系统,上位机,mis系统,C#(一)

    一.效果截图 登录界面 主界面 查看养殖信息界面 添加养殖信息 温度采集实时监控界面1 温度采集实时监控界面2 信息追溯

  8. HDU 5434 Peace small elephant 状压dp+矩阵快速幂

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5434 Peace small elephant  Accepts: 38  Submissions: ...

  9. Eclipse 如何安装反编译插件

    安装反编译插件 1.Help——Eclipse Marketplace 2.输入 Decompiler 搜索并安装此插件 3.根据提示无脑下一步,安装好,重启后(如果还是无法编译,需要把默认打开cla ...

  10. C#高级编程 (第六版) 学习 第二章:C#基础

    第二章 基础 1,helloworld示例: helloworld.cs using System; using System.Collections.Generic; using System.Li ...