Largest (or heaviest) set independent in two matroids on the same ground set. Bipartite matching, colourful spanning trees, spanning trees with type quotas, arborescence existence and “basis with at most one vector per group” are all instances. Three matroids are NP-hard. Matroid basics and the greedy theorem: Matroids.

Each matroid is an oracle with clear(), ins(i), indep(i) (“is the current set plus independent?”). Cost: augmentations, each an -vertex BFS needing oracle rebuilds, so about indep calls for rank ; fine for with cheap oracles.

Exchange graph and the algorithm

For the current common independent set : edge (, ) if ; edge if . Sources , sinks . Augment along a shortest source–sink path; stop when none exists.

struct GraphicMatroid {                                       // independent = forest
    vector<pair<int, int>> ed; vector<int> par;
    GraphicMatroid(int n, const vector<pair<int, int>>& edges) : ed(edges), par(n) {}
    int find(int x) { while (par[x] != x) x = par[x] = par[par[x]]; return x; }
    void clear() { iota(all(par), 0); }
    void ins(int i) { par[find(ed[i].first)] = find(ed[i].second); }
    bool indep(int i) { return find(ed[i].first) != find(ed[i].second); }
};
struct ColorfulMatroid {                                      // independent = at most lim[c] elements of colour c
    vector<int> col, lim, used;
    ColorfulMatroid(const vector<int>& color, const vector<int>& limit) : col(color), lim(limit), used(sz(limit)) {}
    void clear() { fill(all(used), 0); }
    void ins(int i) { used[col[i]]++; }
    bool indep(int i) { return used[col[i]] < lim[col[i]]; }
};
struct LinearMatroid {                                        // independent = linearly independent over GF(2)
    vector<ull> vec, basis;
    LinearMatroid(const vector<ull>& v) : vec(v), basis(64) {}
    ull reduce(ull x) const { for (int b = 63; b >= 0; b--) if ((x >> b & 1) && basis[b]) x ^= basis[b]; return x; }
    void clear() { fill(all(basis), 0); }
    void ins(int i) { ull x = reduce(vec[i]); if (x) basis[63 - __builtin_clzll(x)] = x; }
    bool indep(int i) { return reduce(vec[i]) != 0; }
};
template <class M1, class M2> struct MatroidIntersection {   // oracle: clear(), ins(i), indep(i) = "set + i independent?"
    int n; vector<bool> inSet; M1 m1; M2 m2;
    bool augment() {
        vector<int> pre(n + 1, -1); queue<int> q; q.push(n);  // n = virtual source, treated as "in the set"
        while (sz(q)) {
            int x = q.front(); q.pop();
            if (inSet[x]) {                                   // x -> y if S - x + y independent in M1
                m1.clear();
                for (int i = 0; i < n; i++) if (inSet[i] && i != x) m1.ins(i);
                for (int i = 0; i < n; i++) if (!inSet[i] && pre[i] == -1 && m1.indep(i)) pre[i] = x, q.push(i);
            } else {                                          // y -> x if S - x + y independent in M2
                auto backEdge = [&]() {                       // n: x can be added; -1: no more edges; else next vertex
                    m2.clear();
                    for (int c = 0; c < 2; c++) for (int i = 0; i < n; i++) if ((x == i || inSet[i]) && (pre[i] == -1) == (bool)c) {
                        if (!m2.indep(i)) { if (c) { pre[i] = x; q.push(i); return i; } return -1; }
                        m2.ins(i);
                    }
                    return n;
                };
                for (int y; (y = backEdge()) != -1;) if (y == n) { for (; x != n; x = pre[x]) inSet[x] = !inSet[x]; return true; }
            }
        }
        return false;
    }
    MatroidIntersection(int m, M1 a, M2 b) : n(m), inSet(m + 1), m1(a), m2(b) {
        inSet[n] = true; m1.clear(), m2.clear();
        for (int i = n - 1; i >= 0; i--) if (m1.indep(i) && m2.indep(i)) inSet[i] = true, m1.ins(i), m2.ins(i);   // greedy start
        while (augment()) {}
    }
    int size() const { int s = 0; for (int i = 0; i < n; i++) s += inSet[i]; return s; }
};

Usage: MatroidIntersection<GraphicMatroid, ColorfulMatroid> mi(m, GraphicMatroid(n, edges), ColorfulMatroid(color, limit)); then mi.size() and mi.inSet[i]. The backEdge trick finds the next unvisited with by inserting already-visited elements first, so each vertex needs oracle calls instead of rebuilds.

Matroid union and partition

Put each element into one of matroids (or copies of one) so that every part is independent, maximising the number placed: intersect the direct sum (element copied times, copy goes to matroid ) with the partition matroid “each element at most once”.

template <class M> struct DirectSumMatroid {                 // element e * k + i = element e placed in part i
    int k; vector<M> parts;
    void clear() { for (auto& p : parts) p.clear(); }
    void ins(int x) { parts[x % k].ins(x / k); }
    bool indep(int x) { return parts[x % k].indep(x / k); }
};
template <class M> vector<int> matroidPartition(int m, const vector<M>& ms) {   // part[e] in [0, k) or -1
    int k = sz(ms); if (!k) return vector<int>(m, -1);
    vector<int> color(m * k); for (int x = 0; x < m * k; x++) color[x] = x / k;
    MatroidIntersection<DirectSumMatroid<M>, ColorfulMatroid> mi(m * k, DirectSumMatroid<M>{k, ms}, ColorfulMatroid(color, vector<int>(m, 1)));
    vector<int> part(m, -1);
    for (int x = 0; x < m * k; x++) if (mi.inSet[x]) part[x / k] = x % k;
    return part;
}
  • edge-disjoint spanning trees (Nash-Williams–Tutte): partition the edges into forests; all spanning iff each forest has edges.
  • Arboricity (fewest forests covering all edges) ; find it by trying with matroid partition.
  • Covering by independent sets in any matroid: (Edmonds).
  • Union rank: .

Weighted matroid intersection

best[k] is the maximum weight of a common independent set of size , for every up to the maximum. Each step augments along a source–sink path minimising (cost, number of edges) where entering costs and leaving costs ; Bellman–Ford handles the negative costs (there are no negative cycles). plus oracle costs.

template <class M1, class M2>
vector<ll> weightedMatroidIntersection(int n, M1 m1, M2 m2, const vector<ll>& w, vector<char>& inSet) {
    inSet.assign(n, 0); vector<ll> best = {0}; const ll INF = LLONG_MAX / 4;   // best[k]: max weight of size k
    for (;;) {
        vector<int> S; for (int i = 0; i < n; i++) if (inSet[i]) S.push_back(i);
        vector<vector<int>> g(n); vector<char> src(n), snk(n);
        m1.clear(), m2.clear(); for (int x : S) m1.ins(x), m2.ins(x);
        for (int y = 0; y < n; y++) if (!inSet[y]) src[y] = m1.indep(y), snk[y] = m2.indep(y);
        for (int x : S) {
            m1.clear(), m2.clear(); for (int z : S) if (z != x) m1.ins(z), m2.ins(z);
            for (int y = 0; y < n; y++) if (!inSet[y]) { if (m1.indep(y)) g[x].push_back(y); if (m2.indep(y)) g[y].push_back(x); }
        }
        vector<pair<ll, int>> dist(n, {INF, 0}); vector<int> prv(n, -1);   // (cost, #edges), Bellman-Ford
        for (int y = 0; y < n; y++) if (src[y]) dist[y] = {-w[y], 0};
        for (bool ch = true; ch;) {
            ch = false;
            for (int u = 0; u < n; u++) if (dist[u].first != INF) for (int v : g[u]) {
                pair<ll, int> nd = {dist[u].first + (inSet[v] ? w[v] : -w[v]), dist[u].second + 1};
                if (nd < dist[v]) dist[v] = nd, prv[v] = u, ch = true;
            }
        }
        int end = -1;
        for (int y = 0; y < n; y++) if (snk[y] && dist[y].first != INF && (end == -1 || dist[y] < dist[end])) end = y;
        if (end == -1) break;
        best.push_back(best.back() - dist[end].first);
        for (int v = end; v != -1; v = prv[v]) inSet[v] ^= 1;
    }
    return best;
}

Patterns

Problem
bipartite matchingpartition by left endpointpartition by right endpoint
spanning tree with at most edges of type (rainbow tree when all )graphiccolourful
arborescence rooted at exists / max branchinggraphic (undirected)partition by head, excluded
basis of vectors, at most from each grouplinearcolourful
largest set of vectors independent over GF(2) whose edges also form a forestlineargraphic
tasks with deadlines, each taken by a compatible workertransversal (via matching)uniform / scheduling
  • Matroid parity (pick pairs of elements so that the union is independent) generalises both general matching and matroid intersection; it is polynomial for linear matroids (Lovász; Gabow–Stallmann) and needs exponentially many oracle calls in general. In contests it appears only through general matching.
  • With three constraints (e.g. Hamiltonian path = graphic ∩ in-degree ≤ 1 ∩ out-degree ≤ 1) the problem is NP-hard: look for structure instead.