Matching beyond KMP / Z / Aho–Corasick: Boyer–Moore (sublinear in practice), bit-parallel automata (shift-and / shift-or, Bitap with errors, Myers’ bit-vector edit distance), bitset matching under updates, wildcard matching by FFT, glob patterns, and the containment relation of a pattern set. Rabin–Karp is in String Hashing.

SituationBest toolCost
exact, one pattern, worst-case guaranteeKMP / Z (Prefix Function and Z)
exact, long pattern, large alphabet, practiceBoyer–Moore / Horspoolsublinear on average, worst (bad-character only)
exact, pattern , huge textshift-or / shift-and, two instructions per character
errors (Levenshtein), small Bitap (Wu–Manber)
edit distance / approximate search, any Myers’ bit-vector
many patternsAho–Corasick
text with point updates, occurrences in ranges (CF 914F)bitset shift-and per query
wildcards ? on either sidethree FFT correlations of
* = any substring (glob)split at stars, match pieces greedily with KMP; or DP /
which patterns are substrings of which (CF 590E)Aho–Corasick + bitset closure
vector<int> boyerMoore(const string& t, const string& p) { // bad character + good suffix, all occurrences
    int n = sz(t), m = sz(p); vector<int> res; if (m == 0 || m > n) return res;
    vector<int> last(256, -1); for (int i = 0; i < m; i++) last[(unsigned char)p[i]] = i;
    vector<int> suff(m), good(m + 1, m);                    // suff[i]: longest common suffix of p and p[0..i]
    suff[m - 1] = m;
    for (int i = m - 2; i >= 0; i--) { int k = 0; while (k <= i && p[i - k] == p[m - 1 - k]) k++; suff[i] = k; }   // O(m^2) worst, fine for contests
    for (int i = m - 1, j = 0; i >= 0; i--) if (suff[i] == i + 1) for (; j < m - 1 - i; j++) if (good[j] == m) good[j] = m - 1 - i;   // case 2: prefix = suffix
    for (int i = 0; i <= m - 2; i++) good[m - 1 - suff[i]] = m - 1 - i;   // case 1: reoccurrence of the matched suffix
    for (int s = 0; s <= n - m;) {
        int j = m - 1; while (j >= 0 && p[j] == t[s + j]) j--;
        if (j < 0) { res.push_back(s); s += good[0]; }
        else s += max(good[j], j - last[(unsigned char)t[s + j]]);
    }
    return res;
}
vector<int> horspool(const string& t, const string& p) {   // bad character of the window's last symbol only
    int n = sz(t), m = sz(p); vector<int> res; if (m == 0 || m > n) return res;
    vector<int> shift(256, m); for (int i = 0; i + 1 < m; i++) shift[(unsigned char)p[i]] = m - 1 - i;
    for (int s = 0; s <= n - m; s += shift[(unsigned char)t[s + m - 1]]) if (t.compare(s, m, p) == 0) res.push_back(s);
    return res;
}
vector<int> shiftAnd(const string& t, const string& p) {   // |p| <= 64
    int m = sz(p); vector<int> res; if (m == 0 || m > 64) return res;
    array<ull, 256> B{}; for (int j = 0; j < m; j++) B[(unsigned char)p[j]] |= 1ULL << j;
    ull D = 0, top = 1ULL << (m - 1);
    for (int i = 0; i < sz(t); i++) { D = ((D << 1) | 1) & B[(unsigned char)t[i]]; if (D & top) res.push_back(i - m + 1); }
    return res;
}
vector<int> shiftOr(const string& t, const string& p) {    // complemented: no "| 1" needed
    int m = sz(p); vector<int> res; if (m == 0 || m > 64) return res;
    array<ull, 256> B; B.fill(~0ULL); for (int j = 0; j < m; j++) B[(unsigned char)p[j]] &= ~(1ULL << j);
    ull D = ~0ULL, top = 1ULL << (m - 1);
    for (int i = 0; i < sz(t); i++) { D = (D << 1) | B[(unsigned char)t[i]]; if (!(D & top)) res.push_back(i - m + 1); }
    return res;
}
vector<int> bitapApprox(const string& t, const string& p, int k) {   // end positions with a match of <= k edits, |p| <= 63
    int m = sz(p); vector<int> res; if (m == 0 || m > 63) return res;
    array<ull, 256> B{}; for (int j = 0; j < m; j++) B[(unsigned char)p[j]] |= 1ULL << j;
    vector<ull> R(k + 1); for (int d = 0; d <= k; d++) R[d] = (1ULL << d) - 1;   // d leading pattern chars may be deleted
    ull top = 1ULL << (m - 1);
    for (int i = 0; i < sz(t); i++) {
        ull old = R[0], c = B[(unsigned char)t[i]];
        R[0] = ((R[0] << 1) | 1) & c;
        for (int d = 1; d <= k; d++) {
            ull tmp = R[d];
            R[d] = (((R[d] << 1) | 1) & c) | old | ((old | R[d - 1]) << 1) | 1;   // match | insertion | substitution, deletion
            old = tmp;
        }
        if (R[k] & top) res.push_back(i);
    }
    return res;
}
int myersDistance(const string& p, const string& t, bool search = false) {   // |p| <= 64; global edit distance, or min over substrings of t if search
    int m = sz(p); if (m == 0) return search ? 0 : sz(t);
    array<ull, 256> Peq{}; for (int j = 0; j < m; j++) Peq[(unsigned char)p[j]] |= 1ULL << j;
    ull Pv = ~0ULL, Mv = 0, top = 1ULL << (m - 1); int score = m, best = m;
    for (char ch : t) {
        ull Eq = Peq[(unsigned char)ch], Xv = Eq | Mv, Xh = (((Eq & Pv) + Pv) ^ Pv) | Eq;
        ull Ph = Mv | ~(Xh | Pv), Mh = Pv & Xh;
        if (Ph & top) score++; else if (Mh & top) score--;
        Ph = (Ph << 1) | (search ? 0 : 1); Mh <<= 1;       // row 0: +1 per column (global) or 0 (search)
        Pv = Mh | ~(Xv | Ph); Mv = Ph & Xv;
        best = min(best, score);
    }
    return search ? best : score;
}

Bitset matching with updates

occ = AND_j (mask[p_j] >> j): bit survives iff for all . A point update flips two bits; a query costs , fast enough for (CF 914F). Without updates use KMP / Z; with wildcards, below.

template <int N> struct BitsetMatcher {                     // lowercase text of length n <= N
    int n; string s; vector<bitset<N>> mask;
    BitsetMatcher(const string& str) : n(sz(str)), s(str), mask(26) { for (int i = 0; i < n; i++) mask[s[i] - 'a'][i] = 1; }
    void set(int i, char c) { mask[s[i] - 'a'][i] = 0; s[i] = c; mask[c - 'a'][i] = 1; }
    bitset<N> occurrences(const string& p) const { bitset<N> occ; occ.set(); for (int j = 0; j < sz(p); j++) occ &= mask[p[j] - 'a'] >> j; return occ; }
    int count(int l, int r, const string& p) const {      // occurrences fully inside s[l, r)
        int m = sz(p); if (r - l < m) return 0;
        bitset<N> occ = occurrences(p); int hi = r - m + 1; // starts in [l, hi)
        return ((occ >> l) << (N - (hi - l))).count();
    }
};

Wildcard matching by FFT

Give symbols random non-zero values and the wildcard 0. Position scores : three correlations. The score is a non-zero degree-4 polynomial in the random values when some non-wildcard pair mismatches, so a false match needs a zero modulo both primes, probability per position. Works for any symbol type; counting mismatches per alignment needs one convolution per symbol instead. Uses ntt from NTT.

template <int P, class T> vector<Mint<P>> wildcardScores(const T& text, const T& pat, typename T::value_type wild, mt19937_64& rng) {
    using Z = Mint<P>; int n = sz(text), m = sz(pat);
    map<typename T::value_type, Z> code;
    auto value = [&](typename T::value_type x) { if (x == wild) return Z(0); auto it = code.find(x); if (it == code.end()) it = code.emplace(x, Z(ll(rng() % (P - 1)) + 1)).first; return it->second; };
    int N = 1; while (N < n) N *= 2;                        // cyclic wrap only lands on indices < m - 1, unused
    vector<Z> a1(N), a2(N), a3(N), b1(N), b2(N), b3(N);
    for (int j = 0; j < m; j++) { Z x = value(pat[m - 1 - j]); a1[j] = x; a2[j] = x * x; a3[j] = x * x * x; }   // reversed pattern
    for (int i = 0; i < n; i++) { Z y = value(text[i]); b1[i] = y; b2[i] = y * y; b3[i] = y * y * y; }
    for (vector<Z>* v : {&a1, &a2, &a3, &b1, &b2, &b3}) ntt(*v);
    vector<Z> c(N); for (int k = 0; k < N; k++) c[k] = a3[k] * b1[k] - Z(2) * a2[k] * b2[k] + a1[k] * b3[k];
    ntt(c, true);
    return vector<Z>(c.begin() + m - 1, c.begin() + n);     // c[i + m - 1] scores text[i, i + m)
}
template <class T> vector<int> wildcardMatch(const T& text, const T& pat, typename T::value_type wild) {
    int n = sz(text), m = sz(pat); assert(m > 0); if (m > n) return {};
    mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
    auto s1 = wildcardScores<998244353>(text, pat, wild, rng); auto s2 = wildcardScores<469762049>(text, pat, wild, rng);
    vector<int> pos; for (int i = 0; i <= n - m; i++) if (s1[i] == Mint<998244353>(0) && s2[i] == Mint<469762049>(0)) pos.push_back(i);
    return pos;
}
bool globMatch(const string& t, const string& p) {          // '?' any char, '*' any substring; O(nm) DP
    int n = sz(t), m = sz(p); vector<char> dp(m + 1, 0), nd(m + 1); dp[0] = 1;
    for (int j = 1; j <= m; j++) dp[j] = dp[j - 1] && p[j - 1] == '*';
    for (int i = 1; i <= n; i++) {
        nd[0] = 0;
        for (int j = 1; j <= m; j++) nd[j] = p[j - 1] == '*' ? (nd[j - 1] || dp[j]) : (dp[j - 1] && (p[j - 1] == '?' || p[j - 1] == t[i - 1]));
        dp = nd;
    }
    return dp[m];
}

Pattern containment

For strings of total length , compute “pattern occurs inside pattern ” for all pairs. Scanning pattern through the Aho–Corasick automaton, record at every position only the longest pattern ending there (nearest terminal on the suffix-link chain, excluding the whole of at its last position); every other contained pattern is a substring of a recorded one, so a transitive closure over direct edges recovers the relation. Equal patterns contain each other. Typical next step: maximum antichain maximum matching in the comparability DAG (Dilworth, CF 590E Birthday).

vector<vector<char>> patternContainment(const vector<string>& pats) {   // in[i][j] = pats[i] is a substring of pats[j]
    int k = sz(pats); vector<array<int, 26>> go(1); go[0].fill(0);
    vector<int> link(1, 0), term(1, -1), outLink(1, -1), endNode(k);
    for (int i = 0; i < k; i++) {
        int v = 0;
        for (char ch : pats[i]) { int c = ch - 'a'; if (!go[v][c]) { go[v][c] = sz(go); go.emplace_back(); go.back().fill(0); link.push_back(0), term.push_back(-1), outLink.push_back(-1); } v = go[v][c]; }
        if (term[v] == -1) term[v] = i;
        endNode[i] = v;
    }
    vector<int> q; for (int c = 0; c < 26; c++) if (go[0][c]) q.push_back(go[0][c]);
    for (int i = 0; i < sz(q); i++) {
        int v = q[i]; outLink[v] = term[link[v]] != -1 ? link[v] : outLink[link[v]];   // nearest proper terminal suffix
        for (int c = 0; c < 26; c++) { int u = go[v][c]; if (u) link[u] = go[link[v]][c], q.push_back(u); else go[v][c] = go[link[v]][c]; }
    }
    int W = (k + 63) / 64; vector<vector<ull>> reach(k, vector<ull>(W, 0));   // reach[j] bit i: pats[i] inside pats[j]
    auto mark = [&](int j, int i) { reach[j][i >> 6] |= 1ULL << (i & 63); };
    for (int j = 0; j < k; j++) {
        int v = 0, len = sz(pats[j]);
        for (int p = 0; p < len; p++) {
            v = go[v][pats[j][p] - 'a'];
            int best = p + 1 == len ? outLink[v] : (term[v] != -1 ? v : outLink[v]);
            if (best != -1) mark(j, term[best]);
        }
        mark(j, j); mark(j, term[endNode[j]]);
    }
    for (int i = 0; i < k; i++) mark(term[endNode[i]], i);  // the representative of equal patterns contains every copy
    vector<int> order(k); iota(all(order), 0);
    stable_sort(all(order), [&](int a, int b) { return sz(pats[a]) < sz(pats[b]); });
    for (int j : order) for (int changed = 1; changed;) {   // closure; contained patterns are not longer
        changed = 0;
        for (int i = 0; i < k; i++) {
            if (i == j || !(reach[j][i >> 6] >> (i & 63) & 1)) continue;
            for (int w = 0; w < W; w++) { ull nw = reach[j][w] | reach[i][w]; if (nw != reach[j][w]) reach[j][w] = nw, changed = 1; }
        }
    }
    vector<vector<char>> in(k, vector<char>(k, 0));
    for (int j = 0; j < k; j++) for (int i = 0; i < k; i++) in[i][j] = reach[j][i >> 6] >> (i & 63) & 1;
    return in;
}