专栏文章
bitset神力
算法·理论参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mie5z734
- 此快照首次捕获于
- 2025/11/25 13:57 3 个月前
- 此快照最后确认于
- 2025/12/01 21:17 3 个月前
有一说一 bitset 真是今年算法一匹黑马吧。很多老 stl 也都写过了,不吹不黑,本人 vector 9000 小时,multiset 2000 小时,set 3000 小时,map 4000 小时,pair 3000 小时,算法库里大多数 stl 都上千个小时,真感觉不出来和 bitset 有什么细节差距。只有 bitset 能给我一种刚接触 stl 时的感动。
用法
bitset 是 c++ 的一个 stl,类似于一个 bool 数组,每一位都是 0 / 1,大小位 1bit,可以支持位运算,空间复杂度是 , 是计算机的位数(不是没人告诉我那是 不是 啊)。
-
定义:
bitset<V> b;,V是 bitset 的位数,还可以bitset<V> b("001011"),就可以初始化;(注意:这里是反向的,如果你此时调用for (int i = 0; i < 6; i++) cout << b[i];那么输出的是110100,不过似乎也没多少地方要这么用) -
全局修改:
b.set()/b.reset():设为全 1 / 全 0,b.flip():取反,时间复杂度 ; -
单点修改:
b[pos] = 0/1:直接访问,b.set(pos, val = 0/1):将某位设为 0 / 1,b.reset(pos):将某位设为 0,b.flip(pos):pos 位取反,时间复杂度 ; -
位运算:将这些 0 / 1 视为二进制数与其他 bitset 进行二元位运算(与 / 或 / 异或…),或者左移 / 右移,时间复杂度 ;
-
单点访问:
b[pos],b.test(pos):返回 pos 位的值,时间复杂度 ; -
全局访问:
b.any():b 中是否存在一位为 1,b.none():b 中是否不存在一位为 1,b.count():b 中 1 的数量,时间复杂度 。
应用
维护集合
Set Operation
个集合,给你每个集合的元素, 次询问两个数是否存在于同一个集合内。。
直接枚举
f[i][x] 表示第 个集合有没有 会死, 是 的,于是用 bitset 优化,带一个 的常数,就能过了。code:
CPP#include <bits/stdc++.h>
#define eps 1e-12
#define inf LONG_LONG_MAX
#define int long long
#define endl '\n'
using namespace std;
const int maxn = 1e3 + 10, maxm = 1e4 + 10, mod = 1e9 + 7;
bitset<maxm> f[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int k;
cin >> k;
for (int j = 1; j <= k; j++) {
int x;
cin >> x;
f[i][x] = 1;
}
}
int q;
cin >> q;
while (q--) {
int l, r;
cin >> l >> r;
bool ff = 0;
for (int i = 1; i <= n; i++) {
if (f[i][l] && f[i][r]) {
ff = 1;
cout << "Yes" << endl;
break;
}
}
if (!ff) cout << "No" << endl;
}
return 0;
}
还有另外一种方式,用
f[x][i] 表示 数是否出现在 中,那么询问就是按位与再判断是否有 1 就好了,比较好写。code:
CPP#include <bits/stdc++.h>
#define eps 1e-12
#define inf LONG_LONG_MAX
#define int long long
#define endl '\n'
using namespace std;
const int maxn = 1e3 + 10, int maxm = 1e4 + 10, mod = 1e9 + 7;
bitset<maxn> f[maxm];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int k;
cin >> k;
while (k--) {
int x;
cin >> x;
f[x][i] = 1;
}
}
int q;
cin >> q;
while (q--) {
int l, r;
cin >> l >> r;
cout << ((f[l] & f[r]).any() ? "Yes" : "No") << endl;
}
return 0;
}
CF1097F Alex and a TV Show
个可重集, 次操作形如将集合设为单个数、将集合设为另外两个集的并、将集合设为另外两个集合每个元素的 gcd 的并、询问集合内 的个数。。
模拟赛题不会哭哭。
注意到模 2,于是可以拿 bitset 维护。
于是第一个操作直接改,第二个操作是异或,第四个操作直接查。
第三个操作不太好做,考虑每个 bitset 维护所有约数,那么第三个操作就是按位与。
预处理每个数的约数,第一个也秒了,第四个怎么办……
令原可重集为 ,约数可重集为 ,查询 的个数,莫比乌斯反演:
由于模 2, 相当于 有没有平方因数,预处理每个数没有平方因数的倍数,便可以 预处理, 单个操作。
code:
CPP#include <bits/stdc++.h>
#define eps 1e-12
#define inf LONG_LONG_MAX
#define int long long
#define endl '\n'
using namespace std;
const int maxn = 7e3 + 10, maxm = 1e5 + 10, mod = 1e9 + 7;
bitset<maxn> mu, a[maxm], pre[maxn], pre2[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
mu.set();
for (int i = 2; i * i < maxn; i++)
for (int j = 1; j * i * i < maxn; j++)
mu[i * i * j] = 0;
for (int i = 1; i < maxn; i++)
for (int j = 1; i * j < maxn; j++)
pre[i * j][i] = 1, pre2[i][i * j] = mu[j];
while (m--) {
int op;
cin >> op;
if (op == 1) {
int x, y;
cin >> x >> y;
a[x] = pre[y];
}
else if (op == 2) {
int x, y, z;
cin >> x >> y >> z;
a[x] = a[y] ^ a[z];
}
else if (op == 3) {
int x, y, z;
cin >> x >> y >> z;
a[x] = a[y] & a[z];
}
else {
int x, y;
cin >> x >> y;
cout << ((a[x] & pre2[y]).count() & 1);
}
}
return 0;
}
优化 dp
一般都是可行性 dp,或者状态是 bool 值的 dp,常见的有一系列背包问题。
简单瞎搞题、贪心只能过样例
个数,每个数 ,问 有多少取值。。
由于数据范围很小可以转换为可行性问题,设 表示前 个数能不能达到 ,那么考虑枚举第 位的数 ,则
f[i][j] |= f[i - 1][j - x * x]。很明显会炸,我们考虑用 bitset 的 表示前 个数能达到哪些数,那么状态栏的 就相当于左移了 位,于是
f[i] |= (f[i - 1] << (x * x))。code:
CPP#include <bits/stdc++.h>
#define eps 1e-12
#define inf LONG_LONG_MAX
#define int long long
#define endl '\n'
using namespace std;
const int maxn = 1e2 + 10, maxm = 1e6 + 10, mod = 1e9 + 7;
bitset<maxm> f[maxn];
int l[maxn], r[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
f[0].set(0);
for (int i = 1; i <= n; i++) cin >> l[i] >> r[i];
for (int i = 1; i <= n; i++) {
for (int x = l[i]; x <= r[i]; x++) {
f[i] |= (f[i - 1] << (x * x));
}
}
cout << f[n].count();
return 0;
}
P5020 [NOIP 2018 提高组] 货币系统
给你 种货币的面值,成为 货币系统,让你求出一个货币系统 ,使得 系统的货币种类不超过 系统,并且 系统能凑出的面值 系统也能凑出, 系统不能凑出的面值 系统也不能凑出。。
bitset 优化完全背包,式子就不推了,令
f[x] 表示 能不能表示出来,则 f[x] |= f[x - k * a[i]],枚举 即可。注意到 时相当于 的情况叠加, 相当于 的叠加,所以只需要枚举 即可。
code:
CPP#include <bits/stdc++.h>
#define eps 1e-12
#define inf LONG_LONG_MAX
#define int long long
#define endl '\n'
using namespace std;
const int maxn = 1e5 + 10, maxm = 2.5e4 + 10,mod = 1e9 + 7;
int a[maxn];
bitset<maxm> s;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--) {
s.reset();
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
s[0] = 1;
int ans = 0;
for (int i = 1; i <= n; i++) {
if (!s[a[i]]) {
ans++;
int x = a[i];
while (x <= a[n]) s |= s << x, x <<= 1;
}
}
cout << ans << endl;
}
return 0;
}
其实不优化也能过(
高维偏序
用 bitset 做高维偏序是 , 是维数。
具体来说你的
bitset<N> b[N] 记录的是 是否完全偏序 ,那么先按照每一维排序,得到每一维的 ,满足对于所有 ,a[p[i][j]][i] <= a[p[i][k]][i],即每一维的升序的指针序列。然后再记录一个
bitset<N> tmp,就可以在那个指针序列上,对每一位,用一个指针扫每一个小于等于那一位上数的位置,每扫过一个点就 tmp.set(pos),直到扫不动了就让那一位 & 上 tmp,就得到了某一维上小于等于那一位的所有位置。对每一维都扫一遍,最终为 1 的就是每一维都偏序的,所以每一位的偏序数就是 1 的个数。
没看懂的看代码( 是维数):
CPP#include <bits/stdc++.h>
#define eps 1e-12
#define inf LONG_LONG_MAX
#define int long long
#define endl '\n'
using namespace std;
const int maxn = 1e5 + 10, mod = 1e9 + 7;
bitset<maxn> b[maxn], tmp;
int a[maxn][10], p[10][maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
cin >> a[i][j];
p[j][i] = i;
}
}
for (int d = 1; d <= k; d++) sort(p[d] + 1, p[d] + n + 1, [=](int x, int y) { return a[x][d] < a[y][d]; });
for (int i = 1; i <= n; i++) b[i].set();
for (int d = 1; d <= k; d++) {
tmp.reset();
int id = 1;
for (int i = 1; i <= n; i++) {
while (id <= n && a[p[d][id]][d] <= a[p[d][i]][d]) tmp[p[d][id]] = 1, id++;
if (1 <= p[d][i] && p[d][i] <= n) b[p[d][i]] &= tmp;
}
}
for (int i = 1; i <= n; i++) cout << b[i].count() - 1 << endl;
return 0;
}
P3810 【模板】三维偏序(陌上花开)
注意到是 ,bitset 开不下,需要分组 bitset,就是将 分成多个长为 的段,对每一段做上面的操作,所以 bitset 只需要开 的大小。
code:
CPP#include <bits/stdc++.h>
#define eps 1e-12
#define inf LONG_LONG_MAX
#define int long long
#define endl '\n'
using namespace std;
const int maxb = 1e4 + 10, maxn = 1e5 + 10, mod = 1e9 + 7;
bitset<maxn> b[maxb], tmp;
int a[maxn][5], ans[maxn], p[5][maxn], cnt[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, k, len = 10000;
cin >> n >> k;
k = 3;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
cin >> a[i][j];
p[j][i] = i;
}
}
for (int d = 1; d <= k; d++) sort(p[d] + 1, p[d] + n + 1, [=](int x, int y) { return a[x][d] < a[y][d]; });
for (int l = 1; l <= n; l += len) {
int r = min(l + len - 1, n);
for (int i = 1; i <= r - l + 1; i++) b[i].set();
for (int d = 1; d <= k; d++) {
tmp.reset();
int id = 1;
for (int i = 1; i <= n; i++) {
while (id <= n && a[p[d][id]][d] <= a[p[d][i]][d]) tmp[p[d][id]] = 1, id++;
if (l <= p[d][i] && p[d][i] <= r) b[p[d][i] - l + 1] &= tmp;
}
}
for (int i = 1; i <= r - l + 1; i++) ans[l + i - 1] = b[i].count() - 1;
}
for (int i = 1; i <= n; i++) cnt[ans[i]]++;
for (int i = 0; i < n; i++) cout << cnt[i] << endl;
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...