A machine word holds booleans, and one instruction processes all of them. Any DP or set operation whose inner loop is a boolean OR / AND / XOR / shift over an array can run faster: , . With , is still too slow, but style bounds (about simple word operations) pass in a few seconds. The basics of std::bitset are in Bitset.

ProblemPlainWord-parallel
subset sum up to with items
LCS of strings of length
transitive closure
count triangles or
boolean matrix product (Four Russians: )
Gaussian elimination over GF(2)
“is there a pair/triple with property” over setsAND of two bitsets, then any()

Subset sums, closure, triangles

template<int N> bitset<N> subsetSums(const vector<int>& a) {   // bit s set iff some subset sums to s (s < N)
    bitset<N> b; b[0] = 1;
    for (int x : a) b |= b << x;
    return b;
}
 
template<int N> vector<bitset<N>> transitiveClosure(vector<bitset<N>> g, int n) {   // O(n^3 / 64)
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++) if (g[i][k]) g[i] |= g[k];
    return g;
}
template<int N> long long countTriangles(const vector<bitset<N>>& adj, int n) {
    long long c = 0;
    for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) if (adj[i][j]) c += (adj[i] & adj[j]).count();
    return c / 3;
}

LCS in O(nm/64)

Encode a DP row by its differences: bit of is where the row value increases. Each character of updates all columns at once with one addition, one subtraction and a few logic operations, carried across words:

int lcsBitParallel(const string& s, const string& t) {      // O(|s| * |t| / 64): V <- (V + U) | (V - U), U = V & match
    int m = sz(t), W = (m + 63) / 64;
    vector<vector<ull>> match(256, vector<ull>(W, 0));
    for (int j = 0; j < m; j++) match[(unsigned char)t[j]][j >> 6] |= 1ULL << (j & 63);
    vector<ull> V(W, ~0ULL);                                 // zero bits of V (among the first m) = LCS length
    for (char c : s) {
        const auto& M = match[(unsigned char)c];
        ull carry = 0, borrow = 0;
        for (int k = 0; k < W; k++) {
            ull u = V[k] & M[k];
            ull s1 = V[k] + u, c1 = s1 < u, s2 = s1 + carry, c2 = s2 < s1;
            ull d1 = V[k] - u, b1 = V[k] < u, d2 = d1 - borrow, b2 = d1 < borrow;
            V[k] = s2 | d2; carry = c1 | c2; borrow = b1 | b2;
        }
    }
    int ones = 0;
    for (int k = 0; k < W; k++) {
        ull w = V[k];
        if (k == W - 1 && m % 64) w &= (1ULL << (m % 64)) - 1;
        ones += __builtin_popcountll(w);
    }
    return m - ones;
}

Rules of thumb

  • std::bitset<N> needs a compile-time ; for run-time sizes use a vector<ull> with manual shifts, or a template recursion over sizes () to pick the smallest bitset that fits.
  • Shifts by cost each; OR-ing shifted copies is .
  • _Find_first() and _Find_next(i) iterate set bits in .
  • Popcount: __builtin_popcountll, or bitset::count().

Related: Bitset, Knapsack, Sequence DP.