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.
Proof: shortest augmenting paths work, and the min-max theorem
Exchange lemma. Let be independent, and distinct, with independent for all and dependent for all . Then is independent. Induction on : put . For , is dependent, so the unique circuit of avoids and stays inside ; it is therefore the unique circuit of too. Hence for , is independent iff iff is independent: the hypotheses hold for and the pairs .
Adding a free element. If moreover is independent and every () is spanned by , then satisfies the same hypotheses (the circuit of is still ), so is independent.
Augmentation. Let be a shortest path from to . For use the pairs : each is an arc, an arc with would shortcut the path, and are not sources (a source later on the path would give a shorter path), so they are spanned by ; the lemma gives . For use the pairs in reverse order with the sink as the free element: arcs with would shortcut, and are not sinks. So and grows by one.
Optimality. If no path exists, let be the elements reachable from ; then and . : otherwise some has ; is not a sink, so extending within gives for some , an arc that makes reachable, a contradiction. : otherwise some has ; is not a source, so for some , an arc making reachable, a contradiction. For any common independent : . This proves maximality and Edmonds’ formula .
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: .
Proof: union via intersection, and the covering formula
A set of copies is independent in the direct sum iff each part’s elements (copies with index ) form an independent set of , and it is independent in the partition matroid iff no element is used twice. So common independent sets are exactly partial partitions into independent parts, and maximising the common set maximises the number of placed elements. The union rank formula follows from the intersection min-max applied to these two matroids (a minimising can be taken as all copies of a set , giving from the partition side and from the sum side). Covering: is covered by independent sets iff the union of copies has rank , iff for all , iff for all ; for graphic matroids, the worst can be taken as the edge set of a connected subgraph with .
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;
}Proof sketch: weighted augmentation
Invariant: the current of size is a maximum-weight common independent set of size . By a weight-splitting argument (Frank), this holds iff there are such that is a maximum- basis-like set in and a maximum- one in among sets of its size; under such a splitting the exchange graph with costs / has no negative cycle, so shortest paths are well defined. A minimum-cost path changes the weight by exactly , and choosing the fewest edges among minimum-cost paths guarantees the shortcut-free structure of the unique-matching lemma, so is a common independent set; the potentials from Bellman–Ford give a new weight splitting certifying optimality at size . The sequence
bestis concave in (the step gains are non-increasing).
Patterns
| Problem | ||
|---|---|---|
| bipartite matching | partition by left endpoint | partition by right endpoint |
| spanning tree with at most edges of type (rainbow tree when all ) | graphic | colourful |
| arborescence rooted at exists / max branching | graphic (undirected) | partition by head, excluded |
| basis of vectors, at most from each group | linear | colourful |
| largest set of vectors independent over GF(2) whose edges also form a forest | linear | graphic |
| tasks with deadlines, each taken by a compatible worker | transversal (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.