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 f[110]的含义就是,每个串的某个位置代表该数字是否存在,例如f[2][1024],就表示前2个数,能否组成1024这个数组,用01代表有没有。转移的话就直接 << ,如果之前f[i - 1][j - x * x]这个位置的bit是1,<< 后构成f[i][j]该位也会为1,代表前i个数,第j位的数字存在。,最后统计种类,一个count,美滋滋地数有几个1就行了。
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;
}
posted @ 2022-08-23 11:01  std&ice  阅读(1094)  评论(0编辑  收藏  举报