bitset学习笔记

First Post:

Last Update:

Word Count:
2.1k

Read Time:
10 min

一直不会用 bitset,今天终于下定决心学一下。

bitset 是一个很好用的 STL,且时间复杂度和空间复杂度都很优秀。

bitset 是一个 01 串,每一位占一个 bit。

bitset 的下标是从右往左的。例如 ,那么得到的 bitset 应该是 00010 之类的东西。

所以后面的左移右移才是那样。

如何声明

首先,我们需要引用 <bitset> 头文件。

声明方式为

1
bitset<maxn> s;

maxn 为长度。

修改与运算

bitset 中每一个元素可以通过下标的方式访问。

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

1
s[pos]=1;

bitset 还支持位运算,返回值为一个 bitset。

1
2
3
4
5
s<<=x;
s>>=x;
s&=s1;
s|=s1;
s^=s1;

复杂度

设计算机字长为 ,则一次操作的复杂度为 。

因为 位才占一个整形长度,所以空间复杂度为 。

成员函数

reset

将 bitset 每一位都置为 0。

1
s.reset();

set

如果不传参数,会将每一位都置为 1。复杂度 。

如果传参数,那么第一个参数为 ,第二个参数为 ,意为把第 位置为 。

如果不传 ,那么默认为 1。

1
2
3
s.set();
s.set(1);
s.set(1,0);

test

有一个参数 ,返回第 位的值。

1
s.test(1);

any

若 bitset 内存在 1,则返回 1,否则返回 0。

1
cout<<s.any();

none

若 bitset 内所有位都是 0,则返回 1,否则返回 0。

1
cout<<s.none();

all

若 bitset 内所有位都是 1,则返回 1,否则返回 0。

1
cout<<s.all();

count

返回 bitset 内 1 的个数。

1
cout<<s.count();

flip

把每一位取反。

如果传参数 ,那么就把第 位取反。

1
2
s.flip();
s.flip(1);

_Find_first

返回 bitset 第一个 1 的位置,不存在则返回 bitset 大小。

1
cout<<s._Find_first();

_Find_next

需要传参数 ,返回下标严格大于 的位置中第一个 1 的位置。

1
cout<<s._Find_next(1);

应用

bitset 优化 dp

对于一些可行性 dp,可以 bitset 优化。

P1537 弹珠

这是一道多重背包,不过可以当 01 背包做。

但这是可行性背包,所以用 bitset 优化一下就可以了。

复杂度为

这里给出优化 01 背包的代码。

1
2
f[0]=1;
for(re int i=1;i<=n;++i) f|=(f<<w[i]);
Earn or Unlock *2200

首先可以发现,如果最后一共解锁了 张牌,那么分数为 。

因为我们一开始只有一张牌,想解锁剩下的 张就必须花 的代价。

然后显然解锁的牌可以都拿,那么就是一个前缀和再减去代价。

所以我们只需要判断是否存在一种方案使得恰好解锁 张牌即可。

复杂度 的 dp 显然,考虑优化。

但这是可行性 dp,可以用 bitset 优化。

事实上也是个 01 背包。

复杂度

一些细节:

  1. 可以达到 。
  2. 这里的背包比较特殊,因为如果我们用第 张牌解锁后面的牌,那么恰好解锁 张牌这个状态也就不能为后续提供转移了。所以我们还需要再开个数组,记录每种值能否被恰好取到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=2e5+10;
int n,ans;
int a[maxn],s[maxn];
bitset<maxn> f,g;
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(re int i=1;i<=n;++i) cin>>a[i],s[i]=s[i-1]+a[i];
f[1]=1;
for(re int i=1;i<=n;++i){
f|=(f<<a[i]);
g[i]=f[i];
f[i]=0;
}
for(re int i=n+1;i<=(n<<1);++i) g[i]=f[i],s[i]=s[n];
for(re int i=1;i<=(n<<1);++i) if(g[i]) ans=max(ans,s[i]-i+1);
cout<<ans;
return 0;
}

bitset 结合莫队

[Ynoi2017] 由乃的玉米田

区间询问可离线,考虑莫队。

我们维护两个 bitset,分别为 v1 和 v2。

设值域为 。

如果 在当前区间出现,那么 v1[x]=v2[V-x]=1。

对于减法操作,答案为 (v1&(v1<<x)).any()。

这很好理解。

假设 ,那么当前区间一定存在 和 。

所以我们把 v1 和 (v1<<x) 与一下,看看能否满足即可。

对于加法操作,答案为 (v1&(v2>>(V-x))).any()。

设 ,那么 在 v1 和 在 v2 中是对应的。

假设 ,那么 。

再移项一下,那么 。

这里已经转化成减法了。

因为 是负数,所以把左移变成右移。

所以我们把 v1 和 (v2>>(V-x)) 与一下,看看能否满足即可。

对于乘法操作,我们可以考虑枚举约数,然后看 和 是否都存在。

这个复杂度是 的。

对于除法操作,考虑根号分治。

对于 ,直接暴力枚举,然后看 和 是否都存在即可。

这个复杂度是 的。

对于 ,我们不使用莫队,而是单独处理。

首先将询问离线,然后枚举 。

对于每个 ,我们扫一遍序列。

记 表示 当前最后一次出现的位置。

记 表示满足在 中,同时存在 和 或同时存在 和 的最靠右的 的位置。

设当前扫到 ,那么先更新 ,再用 和 更新 即可。

扫完以后,我们回答以当前枚举到的 作为 的询问。

设询问区间为 ,那么只要 ,该询问就合法。

因为 只有 个,所以复杂度为

综上,总复杂度为 。

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=1e5+10,V=1e5,lim=300;
int n,m,Q,siz;
int a[maxn],buc[maxn],lst[maxn],res[maxn];
struct query{
int op,l,r,x,id;
}q[maxn];
bitset<maxn> v1,v2,ans;
vector<query> q1[maxn];
inline bool cmp(query a,query b){return ((a.l/siz)^(b.l/siz))?(a.l/siz)<(b.l/siz):((a.l/siz)&1)?a.r<b.r:a.r>b.r;}
inline void add(int x){
++buc[x];
v1[x]=v2[V-x]=(buc[x]);
}
inline void del(int x){
--buc[x];
v1[x]=v2[V-x]=(buc[x]);
}
inline void solve(){
for(re int x=1,l;x<=lim;++x){
memset(lst,0,sizeof lst);
memset(res,0,sizeof res);
l=0;
for(re int i=1;i<=n;++i){
lst[a[i]]=i;
if(x*a[i]<=V) l=max(l,lst[x*a[i]]);
if(a[i]%x==0) l=max(l,lst[a[i]/x]);
res[i]=l;
}
for(auto v:q1[x]) ans[v.id]=(v.l<=res[v.r]);
}
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>Q;
for(re int i=1;i<=n;++i) cin>>a[i];
for(re int i=1,op,l,r,x;i<=Q;++i){
cin>>op>>l>>r>>x;
if(op==4&&x<=lim) q1[x].push_back({op,l,r,x,i});
else q[++m]={op,l,r,x,i};
}
solve();
siz=sqrt(n)+1;
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(re int i=1;i<=m;++i){
while(r<q[i].r) add(a[++r]);
while(l>q[i].l) add(a[--l]);
while(r>q[i].r) del(a[r--]);
while(l<q[i].l) del(a[l++]);
if(q[i].op==1) ans[q[i].id]=(v1&(v1<<q[i].x)).any();
if(q[i].op==2) ans[q[i].id]=(v1&(v2>>(V-q[i].x))).any();
if(q[i].op==3) for(re int j=1;j*j<=q[i].x;++j) if(!(q[i].x%j)) if(v1[j]&&v1[q[i].x/j]) ans[q[i].id]=1;
if(q[i].op==4) for(re int j=1;j*q[i].x<=V;++j) if(v1[j]&&v1[j*q[i].x]) ans[q[i].id]=1;
}
for(re int i=1;i<=Q;++i) cout<<(ans[i]?"yuno":"yumi")<<'\n';
return 0;
}

参考文章:

扶苏的bitset浅谈