A trie stores strings as root-to-node paths (insert / lookup independent of the number of words, prefix counts, lexicographic order). Aho–Corasick adds suffix (failure) links and turns the trie into an automaton that finds all occurrences of many patterns in one pass, , and supports DP over “which patterns have I just completed”. The bitwise trie for XOR problems is Binary Trie.

QueryStructureCost
is stored; longest stored prefix of trie
how many words start with ; all of them (autocomplete)trie with pass counts, output
-th word lexicographicallytrie with subtree counts
distinct substringstrie of all suffixes ; use a suffix automaton for
occurrences of each of many patterns in a textAho–Corasick + counts summed up the link tree
list all (position, pattern) matchesAho–Corasick with dictionary (output) links
strings of length avoiding all patterns / containing oneDP over automaton states; matrix power for huge /
patterns inserted and deleted online (CF 710F)binary grouping of static automata total
triehash set
lookup hashing
prefix queries, ordered iterationyesno
memoryhigh: ints with arrays (104 MB for characters, )low

Memory relief: map children ( lookups), or a compressed trie (radix / Patricia tree: chains of single-child nodes become one edge labelled by a substring; nodes, insertion splits edges). A suffix tree is the compressed trie of all suffixes. Deletion: decrement the pass counts (a zero count behaves as absent). Related: suffix automaton (a DAG with linear size over all substrings), eertree (a trie over palindromes), persistent tries.

struct Trie {
    vector<array<int, 26>> nxt; vector<int> pass, ends;     // words through / ending at a node
    Trie() { newNode(); }
    int newNode() { nxt.push_back({}); nxt.back().fill(-1); pass.push_back(0); ends.push_back(0); return sz(nxt) - 1; }
    void insert(const string& s, int d = 1) {             // d = -1 erases one copy (must be present)
        int v = 0; pass[v] += d;
        for (char ch : s) { int c = ch - 'a'; if (nxt[v][c] == -1) nxt[v][c] = newNode(); v = nxt[v][c]; pass[v] += d; }
        ends[v] += d;
    }
    int walk(const string& p) const { int v = 0; for (char ch : p) { v = nxt[v][ch - 'a']; if (v == -1) return -1; } return v; }
    bool contains(const string& s) const { int v = walk(s); return v != -1 && ends[v] > 0; }
    int countPrefix(const string& p) const { int v = walk(p); return v == -1 ? 0 : pass[v]; }
    int longestStoredPrefix(const string& w) const {        // length of the longest stored word that is a prefix of w, -1 if none
        int v = 0, best = ends[0] > 0 ? 0 : -1;
        for (int i = 0; i < sz(w); i++) { v = nxt[v][w[i] - 'a']; if (v == -1) break; if (ends[v] > 0) best = i + 1; }
        return best;
    }
    string kth(int k) const {                               // k-th (0-indexed) stored word in lexicographic order, with multiplicity
        string res; int v = 0;
        while (true) {
            if (k < ends[v]) return res;
            k -= ends[v];
            for (int c = 0; c < 26; c++) { int u = nxt[v][c]; if (u == -1 || pass[u] <= 0) continue; if (k < pass[u]) { res += 'a' + c; v = u; break; } k -= pass[u]; }
        }
    }
};
 
struct AhoCorasick {                                        // lowercase patterns, full goto automaton
    vector<array<int, 26>> go; vector<int> link, out, termOf, order; vector<vector<int>> endsHere;
    int addPattern(const string& s, int id) {
        if (go.empty()) newNode();
        int v = 0;
        for (char ch : s) { int c = ch - 'a'; if (!go[v][c]) { int u = newNode(); go[v][c] = u; } v = go[v][c]; }
        endsHere[v].push_back(id); termOf.push_back(v); return v;
    }
    int newNode() { go.push_back({}); link.push_back(0); out.push_back(-1); endsHere.push_back({}); return sz(go) - 1; }
    void build() {                                          // BFS; out[v] = nearest node on the link chain (excluding v) where a pattern ends
        if (go.empty()) newNode();
        order = {0};
        for (int i = 0; i < sz(order); i++) {
            int v = order[i];
            for (int c = 0; c < 26; c++) {
                int u = go[v][c];
                if (u) {                                    // tree edge (the root is never a child)
                    link[u] = v == 0 ? 0 : go[link[v]][c];
                    out[u] = endsHere[link[u]].empty() ? out[link[u]] : link[u];
                    order.push_back(u);
                } else go[v][c] = v == 0 ? 0 : go[link[v]][c];
            }
        }
    }
    vector<ll> countOccurrences(const string& text, int numPatterns) {   // occurrences of each pattern, O(n + m)
        vector<ll> hit(sz(go), 0); int v = 0;
        for (char ch : text) { v = go[v][ch - 'a']; hit[v]++; }
        for (int i = sz(order) - 1; i > 0; i--) hit[link[order[i]]] += hit[order[i]];   // push counts up the link tree
        vector<ll> res(numPatterns, 0); for (int id = 0; id < numPatterns; id++) res[id] = hit[termOf[id]];
        return res;
    }
    vector<pair<int, int>> allMatches(const string& text) { // (end position, pattern id)
        vector<pair<int, int>> res; int v = 0;
        for (int i = 0; i < sz(text); i++) {
            v = go[v][text[i] - 'a'];
            for (int u = endsHere[v].empty() ? out[v] : v; u > 0; u = out[u]) for (int id : endsHere[u]) res.push_back({i, id});
        }
        return res;
    }
    ll countAvoiding(int L, int A, ll mod) {                // strings of length L over the first A letters containing no pattern
        int n = sz(go); vector<char> bad(n, 0);
        for (int v : order) bad[v] = !endsHere[v].empty() || (v && bad[link[v]]);
        vector<ll> dp(n, 0); dp[0] = 1;
        for (int i = 0; i < L; i++) {
            vector<ll> nd(n, 0);
            for (int v = 0; v < n; v++) if (dp[v]) for (int c = 0; c < A; c++) { int u = go[v][c]; if (!bad[u]) nd[u] = (nd[u] + dp[v]) % mod; }
            dp = nd;
        }
        ll r = 0; for (ll x : dp) r = (r + x) % mod; return r;
    }
};

Dynamic Aho–Corasick

Patterns are inserted and deleted online and queries ask for the total number of occurrences of the current patterns in a text (CF 710F). Keep groups of sizes like a binary counter (static-to-dynamic); each group is a static automaton whose cnt[v] is the number of patterns ending on the link chain of . Deletions go into a second structure and are subtracted.

struct DynamicAhoCorasick {
    struct Static {
        vector<array<int, 26>> go; vector<int> link, cnt; vector<string> pats;
        void build() {
            go.assign(1, {}), link.assign(1, 0), cnt.assign(1, 0);
            for (auto& p : pats) {
                int v = 0;
                for (char ch : p) { int c = ch - 'a'; if (!go[v][c]) { go[v][c] = sz(go); go.push_back({}), link.push_back(0), cnt.push_back(0); } v = go[v][c]; }
                cnt[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]; cnt[v] += cnt[link[v]];
                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]; }
            }
        }
        ll count(const string& text) const { if (pats.empty()) return 0; ll res = 0; int v = 0; for (char ch : text) { v = go[v][ch - 'a']; res += cnt[v]; } return res; }
    };
    struct Grouped {
        vector<Static> groups;
        void insert(const string& p) {
            vector<string> cur = {p}; int k = 0;
            while (k < sz(groups) && !groups[k].pats.empty()) { cur.insert(cur.end(), all(groups[k].pats)); groups[k] = Static(); k++; }
            if (k == sz(groups)) groups.emplace_back();
            groups[k].pats = move(cur); groups[k].build();
        }
        ll count(const string& text) const { ll r = 0; for (auto& g : groups) r += g.count(text); return r; }
    };
    Grouped added, removed;
    void insert(const string& p) { added.insert(p); }
    void erase(const string& p) { removed.insert(p); }       // p must currently be present
    ll count(const string& text) const { return added.count(text) - removed.count(text); }
};

Binary trie (pointer)

The trie over bits of integers (most significant first) answers XOR queries by greedy walks; full treatment and code in Binary Trie.

QueryMethod
max / min of over stored walk the opposite / same bit
count stored with ()at each 1-bit of add the subtree on the matching side, descend the other (complement)
-th smallest , -th stored value, count subtree counts
maximum XOR subarray / pairprefix XORs inserted one by one
subarrays with XOR in with = count below
XOR with a bound walk with a tight flag as in digit DP
max XOR with any element of persistent trie, counts differenced between versions
binary trieXOR basis
storesa multiset of valuesa linear span
max XOR over all subsetsnoyes
count values with a property, deletionyesno / hard
memory

“Choose an element” wants the trie, “choose a subset” wants the XOR basis. The greedy is optimal because .