bitset

简介

$bitset$ 确实是一个很好用的东西

分组 bitset

大概就是因为空间不够,所以连续 $B$ 个开一个 $bitset$

01背包

  1. 一般的背包的时间复杂度大概是 $O(\frac{nm}{w})$
  2. 如果 $n$ 个物体的体积和为 $O(n)$ 级别的数字,那么我们可以做到 $O(\frac{n\sqrt n}{w})$,具体做法:不同的体积只有 $O(\sqrt n)$ 种,对每种体积做二进制优化的多重背包,时间复杂度看似是 $O(\frac{n\sqrt n\log n}{w})$,但其实是 $O(\frac{n\sqrt n}{w})$

异或方程组

字符串匹配

设模板串长度为 $n$,匹配串长度为 $m$,那么使用 $bitset$ 可以做到 $O(\frac{nm}{w})$,做法就是对于模板串每个字符开一个 $bitset$ 存储该字符出现在哪个位置,然后枚举匹配串的每个位置,与对应的模板的 $bitset$ 与即可

1
2
3
for (int i = 1; i <= n; ++i) B[ch(s[i])].set(i); 
ans.set();
for (int i = l; i; --i) ans &= B[ch(c[i])], i > 1 && (ans >>= 1, 0);

例题

  1. 简要题意:给定一个长度为 $n$ 的字符串 $S$,现在有 $m$ 次操作,操作有两种,第一种操作是给定一个整数 $x$ 和一个字符 $ch$,将 $S_x$ 改成 $ch$;第二种操作给定 $[l,r]$ 和字符串 $T$,求字符串 $T$ 在 $S[l..r]$ 中出现了多少次

    $|S|,m,\sum |T|\le 10^5$

    简要题解:注意到数据范围全部是 $10^5$,我们直接考虑 $bitset$,第一个操作显然可以做到 $O(1)$,第二个操作我们直接做字符串匹配然后通过左移和右移把区间 $[l,r]$ 外的变成 $0$ 即可,时间复杂度 $O(\frac{n^2}{w})$

    CF 914F Substrings in a String(bitset)

    1
    2
    bitset<N + 1> ans;
    ans = ans >> x << N + x - y; // 将 [x,y] 外的区间都变成 0
  2. 简要题意:给定一个长度为 $n$ 的字符串 $S$,现在有 $m$ 次询问,每次询问规定一个整数 $k_i$ 和一个字符串 $T_i$,对于每次询问要求找到一个字符串 $B$ 满足 $B$ 是 $S$ 的子串,且 $T_i$ 在 $B$ 中至少出现了 $k_i$ 次,保证每次询问的 $T_i$ 不同

    $|S|,n,\sum |T|\le 10^5$

    简要题解:首先我们有一个经典结论,对于一个长度为 $n$ 的字符串 $S$ 和 $m$ 个长度和为 $O(n)$ 的字符串 $T_i$,这 $m$ 个字符串在 $S$ 中出现的总次数为 $O(n\sqrt n)$

    那么我们只需要能快速求出,每个字符串在 $S$ 中所有出现位置,然后暴力枚举每个出现位置即可,求出现位置可以使用 $bitset$,同时 $bitset$ 有一个函数可以实现 $O(\frac{n}{w}+c)$ 找出所有的 $1$ ,时间复杂度 $O(\frac{n^2}{w}+n\sqrt n)$

    CF 963D Frequency of String

    1
    2
    bitset<N + 1> ans;
    for (int p = ans._Find_first(); p <= N; p = ans._Find_next(p)) vec.push_back(p); // 找所有的 1

偏序

我们对于每个点开一个 $bitset$ 存储有多少点小于它,然后每一维小于它的数与起来即可

例题

  1. 三维偏序+分组 $bitset$,时间复杂度 $O(nB+\frac{n^2}{w})$,空间复杂度 $O(\frac{n^2}{wB})$

    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
    66
    67
    68
    69
    70
    71
    #include <iostream>
    #include <bitset>
    #include <algorithm>
    #include <cmath>
    #include <map>
    #include <vector>
    #define maxn 100010
    #define sqtn 10010
    using namespace std;

    int n, m;

    struct node {
    int x[3], rk[3], id;
    } a[maxn]; int I;
    inline bool cmp(const node &u, const node &v) {
    if (u.x[I] == v.x[I])
    return make_tuple(u.x[0], u.x[1], u.x[2], u.id) <
    make_tuple(v.x[0], v.x[1], v.x[2], v.id);
    return u.x[I] < v.x[I];
    }

    const int N = 100000;
    bitset<N + 1> B[3][sqtn];

    int l[sqtn], r[sqtn], bl[maxn], blo, num, d[3][maxn];
    void init_blo(int n) {
    blo = min(n, 10); num = (n + blo - 1) / blo;
    for (int i = 1; i <= n; ++i) bl[i] = (i - 1) / blo + 1;
    for (int i = 1; i <= num; ++i) {
    l[i] = (i - 1) * blo + 1;
    r[i] = i * blo;
    } r[num] = n;

    for (int o = 0; o < 3; ++o) {
    I = o; sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= n; ++i) a[i].rk[I] = i, d[o][i] = a[i].id;
    for (int i = 1; i <= num; ++i) {
    B[o][i] = B[o][i - 1];
    for (int j = l[i]; j <= r[i]; ++j) B[o][i].set(a[j].id);
    }
    }
    }

    int ans[maxn];
    int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
    a[i].id = i;
    for (int j = 0; j < 3; ++j) cin >> a[i].x[j];
    } init_blo(n); sort(a + 1, a + n + 1, [](const node &u, const node &v) { return u.id < v.id; });
    for (int i = 1; i <= n; ++i) {
    bitset<N + 1> res, bits; res.set();
    for (int o = 0; o < 3; ++o) {
    int Bl = bl[a[i].rk[o]]; bits = B[o][Bl - 1];
    for (int j = l[Bl]; j < a[i].rk[o]; ++j) bits.set(d[o][j]);
    res &= bits;
    } ans[i] = res.count();
    } map<tuple<int, int, int>, int> mp;
    for (int i = n; i; --i) {
    tuple<int, int, int> t = make_tuple(a[i].x[0], a[i].x[1], a[i].x[2]);
    if (mp.count(t)) ans[i] += mp[t];
    ++mp[t];
    } vector<int> res(n);
    for (int i = 1; i <= n; ++i) ++res[ans[i]];
    for (int i = 0; i < n; ++i) cout << res[i] << "\n";
    return 0;
    }

有向图连通性

我们直接拿 $bitset$ 存储每个点可以到的点,然后在拓扑序逆序上维护这个东西即可,对于不是 $DAG$ 的图需要先缩点

一般的做法时间复杂度就是 $O(\frac{nm}{w})$,对于稠密图即 $m=O(n^2)$,我们可以将序列分块,不妨设块大小为 $B$,那么对于每个块,我们记录 $2^B$ 个子集的 $bitset$ 的并,然后块内我们 $B^2$ 暴力更新,块间我们求出每个点能到这个块内的多少点,这样本来需要做若干次 $bitset$ 交,但是因为我们预处理了 $2^B$ 个子集的答案,所以这里可以只做一次 $bitset$ 交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void solve() { // g[u][v] 表示是否存在 u 到 v 的边, tp 表示拓扑序, f[u] 表示 u 可以到的点的 bitset
topsort(); init_blo(n); reverse(tp + 1, tp + n + 1);
int M = (1 << B) - 1;
for (int i = 1; i <= n; ++i) f[i].set(i);
for (int i = 1; i <= num; ++i) {
for (int j = l[i]; j <= r[i]; ++j)
for (int k = j + 1; k <= r[i]; ++k)
if (g[tp[k]][tp[j]]) f[tp[k]] |= f[tp[j]];
for (int S = 1; S <= M; ++S) {
int k = l[i] + __lg(lowbit(S));
bits[S] = f[tp[k]] | bits[S ^ lowbit(S)];
}
for (int j = r[i] + 1; j <= n; ++j) {
int S = 0;
for (int k = 1; k <= B; ++k)
if (g[tp[j]][tp[l[i] + k - 1]]) S |= 1 << k - 1;
if (S) f[tp[j]] |= bits[S];
}
}
}

例题

  1. 简要题意:给定 $n$ 个点 $m$ 条边的 $DAG$,求最多能删掉多少条边的同时不改变原图的连通性

    $n\le 3\times 10^4,m\le 10^5$

    简要题解:容易发现删边的操作没有后效性,所以我们能删就删,如果边 $(u,v)$ 可以被删掉,那么则意味着一定存在 $x$ 使得 $u$ 可以到达 $x$ 且 $x$ 可以到 $v$,这个东西可以直接用 $bitset$ 维护,时间复杂度 $O(\frac{nm}{w})$

    Luogu P6134 [JSOI2015]最小表示

  2. 简要题意:给定一个 $n$ 个点 $m$ 条边的 $DAG$,现在有 $q$ 次询问,每次询问给定 $u,v,l,r$,问从 $u$ 出发,只经过编号在 $[l,r]$ 内的边能否到达 $v$

    $n,q\le 50000,m\le 100000$

    简要题解:注意询问为 $DAG$ 可达性,显然只能使用 $bitset$,另外每个询问都固定了一个区间内的边,我们考虑离线

    令 $S_i$ 表示有第 $i$ 条边的询问集合,同时我们令 $f_{u,i}$ 从 $u$ 出发,只经过 $[l_i,r_i]$ 内的边能否到达 $v_i$,这个东西我们拿 $bitset$ 优化,然后拓扑排序后,我们按照逆序更新 $f$ 即可,具体的,对于第 $k$ 条边 $(u,v)$,$f_u=f_u~or~(f_v~and~S_k)$,至于如何求 $S_k$,我们将询问离线后扫描线即可,但是由于空间限制,所以我们 $S_k$ 需要使用分组 $bitset$ 来存储

    时间复杂度 $O(\frac{qm}{w}+Bm)$,空间复杂度 $O(\frac{nq}{Bw})$,$B$ 取 $5$ 即可满足空间限制

    2022杭电多校3 J Range Reachability Query