Tree Matching
You are given a tree consisting of n nodes.
A matching is a set of edges where each node is an endpoint of at most one edge. What is the maximum number of edges in a matching?
Input:
- First line: integer
n— number of nodes (1..n). - Next
n-1lines: edgesa bdescribing an undirected tree edge.
Output:
- Print one integer: the maximum size of a matching.
Constraints:
1 ≤ n ≤ 2 · 10^5
Example:
Input:
5
1 2
1 3
3 4
3 5
Output:
2
Explanation: One possible matching is (1,2) and (3,4).
Solution
We want the largest set of edges such that no two chosen edges share an endpoint.
On a tree, we can solve this with DP after rooting the tree. The key local choice is whether a node is already “used” by matching it with its parent, because then it cannot match with any child.
DP idea
Root the tree at node 1. For each node v, compute:
dp0[v]: maximum matching size in the subtree ofvwhen v is NOT matched with its parent (sovis free: it may match with at most one of its children, or none)dp1[v]: maximum matching size in the subtree ofvwhen v IS matched with its parent (sovis already used, cannot match with any child)
Answer will be dp0[root] because the root has no parent.
Recurrence
Let children(v) be neighbors except the parent.
1) If v is matched with its parent (dp1[v])
Then v cannot match with children, so each child c must be in state “not matched with parent”:
[ dp1[v] = _{c children(v)} dp0[c] ]
2) If v is NOT matched with its parent (dp0[v])
Two possibilities:
vmatches with no child then all children are independent in statedp0:
[ base = _{c} dp0[c] ]
vmatches with exactly one childuchoosing edge(v,u)adds+1, and forces childuto be matched with its parent (dp1[u]). All other children remaindp0.
So if we match v with a specific child u, total is:
[ base - dp0[u] + dp1[u] + 1 ]
We choose the best u, or choose none:
[ dp0[v] = (base, _{u children(v)} (base - dp0[u] + dp1[u] + 1)) ]
SRTBOT proof
S — State
dp0[v] = best matching size inside subtree of v when v is free with respect to its parent. dp1[v] = best matching size inside subtree of v when v is already matched to its parent.
These states cover all possibilities because the only interaction between a subtree and the rest of the tree is through the edge to the parent.
R — Recurrence
- If
vis used by its parent (dp1[v]), it cannot be endpoint of any other matching edge → it cannot match with children → sum ofdp0over children. - If
vis free (dp0[v]), it may match with at most one child. Either match none (base) or match one childu, converting only that child’s state todp1[u]and adding 1 edge.
Thus the formulas above are necessary and sufficient.
T — Transition
Compute children first via DFS, then:
base = sum(dp0[child])dp1[v] = basedp0[v] = max(base, max(base - dp0[u] + dp1[u] + 1))
B — Base cases
For a leaf node v:
- No children → sums are 0
dp1[v] = 0(can’t match with children)dp0[v] = 0(no child to match with)
Correct.
O — Order of computation
Post-order DFS: compute DP for children before parent. Each node uses only children DP values, so dependencies are satisfied.
T — Time and space complexity
Each edge is processed a constant number of times.
- Time:
O(n) - Space:
O(n)for adjacency + DP + recursion.
Final answer
Print dp0[1] after computing DP from root 1.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> g;
vector<long long> dp0, dp1;
void dfs(int v, int p) {
long long base = 0;
// First: solve children
for (int to : g[v]) {
if (to == p) continue;
dfs(to, v);
base += dp0[to];
}
// If v is matched with parent -> cannot match any child
dp1[v] = base;
// If v is not matched with parent -> either match with no child, or with exactly one child
dp0[v] = base;
for (int u : g[v]) {
if (u == p) continue;
long long candidate = base - dp0[u] + dp1[u] + 1; // match (v,u)
dp0[v] = max(dp0[v], candidate);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
g.assign(n + 1, {});
for (int i = 0; i < n - 1; i++) {
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
dp0.assign(n + 1, 0);
dp1.assign(n + 1, 0);
int root = 1;
dfs(root, -1);
cout << dp0[root] << "\n";
return 0;
}