bitset的用法及例题(对DP过程的优化)
bitset这容器有点离谱,卡常优化空间神器。
什么是bitset?
bitset是c++ STL里面的一个容器,可以理解为存放01串的,很奇怪,bool[]不也一样能实现这个功能?不是这样的,bool每个元素占一个字节,也就是8bit,而bitset中每个串中的01值每个只占一个bit!!!
bitset的声明
bitset<10000> s
长度为1e4的01串,无参构造,元素全部为0。
s[i]
可以像数组一样取得某个位置的值。
bitset<4> s(string("1001")) 用字符串填充,不足高位补0
bitset<10> s(5) 用5的二进制位填充,不足高位为0
cout << s;
直接输出一个01串,且只能用cout输出。
bitset<1000> f[100]
支持多维,代表有100个长度为1000的01串(默认为0),这个在DP的优化中经常用到,一个01串来代表一个状态!
bitset的其他用法
1、常用的函数
s.count() 返回有多少个1
s.any() 至少有一个1返回true,反之false
s.none() 全为0返回true,反之false
s.set() 将每位全部赋值为1
s.set(u, v) 将第u位赋值为v, v只能取值0或者1
s.reset() 将每位全部赋值为0
s.reset(k) 将第k位赋值为0
位运算
支持以下位运算,对于每一个bitset也就是每一个01串。
~:按位取反
&:按位与
|:按位或
^:按位异或
<< >>:左/右移(常用)
==/!=:判两个bitset是否相等
例题
Set Operation
Des
传送门
大概题意是给你n个集合(n<=1000)每个集合最多10000个数,每个数最大为10000,最多2e5次查询,询问是否存在x,y是否在同一个集合中。
Solution
我们可以直接设置一个二维数组,bool f[1001][10001],然后暴力进行查询,复杂度1e4 * 2e5 = 2e9,妥妥的T飞了,虽然时限给了3s,我们使用bitset优化,因为bitset内部是int拼接而成的,一般能将代码复杂度/32,这样就可以卡过这个题了。
Code
#include <cstdio>
#include <cstring>
#include <bitset>
#define RE(i,a,b) for(int i = a; i <= b; ++i)
using namespace std;
const int N = 1e4 + 10;
int n, q;
bitset<N> f[1010];
signed main()
{
scanf("%d", &n);
RE(i, 1, n)
{
int m, x;
scanf("%d", &m);
RE(j, 1, m)
{
scanf("%d", &x);
f[i][x] = 1;
}
}
int l, r;
scanf("%d", &q);
while(q-- && scanf("%d %d", &l, &r))
{
int tag = 0;
RE(i, 1, n)
{
if(f[i][l] && f[i][r])
{
tag = 1;
break;
}
}
printf("%s\n", tag ? "Yes" : "No");
}
return 0;
}
简单瞎搞题
Des
传送门
题意说的很清楚了,有n个数,x可以取值li-ri,问sumXi可能的值有多少。
Solution
分组背包方案数问题,每个组选一个数,考虑状态描述:f[i][j],前i个数,和为j的数字是否存在,转移方程如下:
f[i]][j] |= f[i - 1][j - x * x]
这样很显然,空间炸了,虽然bool能接受,但是总不够意思,时间复杂度也到了1e10的级别。 我们考虑采用bitset优化,开100个1e6长度的01串,于是我们可以将优化成这样。
f[i] |= (f[i - 1] << (x * x))
前面说过,每个bitset串都可以进行位运算操作,bitset
Code
#include <cstdio>
#include <cstring>
#include <bitset>
#define RE(i,a,b) for(int i = a; i <= b; ++i)
using namespace std;
const int N = 1e6 + 10;
int n, L[110], R[110];
bitset<N> f[110];
signed main()
{
scanf("%d", &n);
RE(i, 1, n) scanf("%d %d", &L[i], &R[i]);
f[0].set(0); //等价f[0][0] = 1
RE(i, 1, n)
{
for(int j = L[i]; j <= R[i]; ++j)
f[i] |= (f[i - 1] << (j * j));
}
printf("%d\n", f[n].count());
return 0;
}
BackPack
Des
传送门
这是多校第一场的第二题,也是通过这个题才认识bitset,题意很简单,n个数,背包容量m,问装满背包时候,背包里面异或值最大可能是多少?
Solution
考虑朴素方程bool
f[i][j][k] 前i个数,异或值为j,体积为k的方案是否存在
转移方程如下:
f[i][j][k] = f[i - 1][j][k] 不选i
f[i][j][k] |= f[i - 1][j ^ w][k - v] 选i
这里解释一下选i的转移方程, w是价值,v是体积,j ^ w ^ w = j, 所以要从j ^ w转移过来,我们考虑一下这个方程,很显然,空间炸了,我们使用滚动数组优化可以到二维,我们可以写出如下的伪代码。
f[0][0] = 1;//异或值为0体积为0的方案存在
for(int i = 1; i <= n; ++i)
{
cin >> w >> v;//价值 体积 题目是反过来的
for(int j = 0; j < 1024; ++j)
for(int k = m; k >= 0; -k)
{
f[j][k] = f[j][k];//不选
if(m >= v) f[j][k] |= f[j ^ w][k - v];
}
}
int ans = -1;
for(int i = 0; i < 1024; ++i)
if(f[i][m]) ans = i;
cout << ans;
嘶,这空间降下来了,时间复杂度有点高啊!不慌,如法炮制例二,我们将k第三维的信息放到bitset中。
bitset<1010> f[2][N]
前面的2是一个滚动数组的优化,j代表异或和是几,f[][j][k]代表异或和为j,体积为k是否存在。k存在于bitset中表示,于是优化掉了一维,状态转移方程如下:
f[x][j] = f[x ^ 1][j] (k < v)
f[x][j] = f[x ^ 1][j] | f[x ^ 1][j ^ w] << v (k >= v)
这样就完成了所有的优化,时间空间复杂度都降了下来。
Code
#include <cstdio>
#include <cstring>
#include <bitset>
using namespace std;
const int N = 1030;
int n, m, v, w;
bitset<N> f[2][N];
signed main()
{
int T;
scanf("%d", &T);
while(T-- && scanf("%d%d", &n, &m))
{
for(int i = 0; i < 1024; ++i) f[0][i] = f[1][i] = 0;
f[0][0][0] = 1;
for(int i = 1, x = 1; i <= n; ++i, x ^= 1)
{
scanf("%d %d", &v, &w);
for(int j = 0; j < 1024; ++j)
f[x][j] = f[x ^ 1][j ^ w] << v | f[x ^ 1][j];
}
int ok = -1;
for(int i = 0; i < 1024; ++i)
if(f[n & 1][i][m]) ok = i;
printf("%d\n", ok);
}
return 0;
}