bitset神力
有一说一 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():取反,时间复杂度O(\frac{n}{\omega}) ; -
单点修改:
b[pos] = 0/1:直接访问,b.set(pos, val = 0/1):将某位设为 0 / 1,b.reset(pos):将某位设为 0,b.flip(pos):pos 位取反,时间复杂度O(1) ; -
位运算:将这些 0 / 1 视为二进制数与其他 bitset 进行二元位运算(与 / 或 / 异或…),或者左移 / 右移,时间复杂度
O(\frac{n}{\omega}) ; -
单点访问:
b[pos],b.test(pos):返回 pos 位的值,时间复杂度O(1) ; -
全局访问:
b.any():b 中是否存在一位为 1,b.none():b 中是否不存在一位为 1,b.count():b 中 1 的数量,时间复杂度O(\frac{n}{\omega}) 。
应用
维护集合
Set Operation
$1\le n\le 10^4, 1\le q \le 2\times10^5,1\le c_i\le 10^5$。
直接枚举 f[i][x] 表示第
code:
#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] 表示
code:
#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
$1\le n\le 10^5, 1\le x\le 7000, 1\le m\le 10^6$。
模拟赛题不会哭哭。
注意到模 2,于是可以拿 bitset 维护。
于是第一个操作直接改,第二个操作是异或,第四个操作直接查。
第三个操作不太好做,考虑每个 bitset 维护所有约数,那么第三个操作就是按位与。
预处理每个数的约数,第一个也秒了,第四个怎么办……
令原可重集为
由于模 2,
code:
#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,常见的有一系列背包问题。
简单瞎搞题、贪心只能过样例
$1\le n, l_i, r_i\le 100$。
由于数据范围很小可以转换为可行性问题,设 f[i][j] |= f[i - 1][j - x * x]。
很明显会炸,我们考虑用 bitset 的 f[i] |= (f[i - 1] << (x * x))。
code:
#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 提高组] 货币系统
给你
n 种货币的面值,成为A 货币系统,让你求出一个货币系统B ,使得B 系统的货币种类不超过A 系统,并且A 系统能凑出的面值B 系统也能凑出,A 系统不能凑出的面值B 系统也不能凑出。
bitset 优化完全背包,式子就不推了,令 f[x] 表示 f[x] |= f[x - k * a[i]],枚举
注意到
code:
#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 的个数。
没看懂的看代码(
#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 【模板】三维偏序(陌上花开)
注意到是
code:
#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;
}