Editorial: Graph Cutting (Two Vertices, Fixed Size)
After E0 (count all connected supersets of $\{a,b\}$), we restrict to $|S|=d$. This is the main technical step before E2 (all pairs, minimal subgraph) and E (three vertices).
Let $P(a,b)$ have $L$ vertices. Every valid $S$ contains $P(a,b)$, so
$$|S| = L + t$$where $t \ge 0$ is the number of vertices taken from side branches (subtrees hanging off the path, not on it). We need $t = d - L$; if $d < L$, answer $0$.
For a side vertex $x$, define
$$F_x(z)=\sum_{s \ge 1} \mathrm{ways}_s(x)\, z^s,$$where $\mathrm{ways}_s(x)$ counts connected subsets of $x$’s subtree that contain $x$ and have size $s$.
Leaf: $F_x(z)=z$.
Recursion: process side children $y$ of $x$. Each child is skipped ($1$) or contributes $F_y(z)$:
$$F_x(z)=z\cdot\prod_{y}\bigl(1+F_y(z)\bigr).$$Implementation: start cur[1]=1, for each child cur = conv_skip(cur, child) where conv_skip multiplies by $(1+F_y)$ — copy coefficients (skip) plus shift-convolve (take).
At $v \in P(a,b)$, branches are independent:
$$G_v(z)=\prod_{x \text{ side neighbor of } v}\bigl(1+F_x(z)\bigr).$$Again use conv_skip for each factor $(1+F_x)$.
Side regions at different path vertices are disjoint:
$$G(z)=\prod_{v \in P(a,b)} G_v(z).$$Answer: coefficient of $z^{d-L}$ in $G(z)$.
Star center $1$, leaves $2,3,4$; $a=2$, $b=3$, $d=4$.
- Path length $L=3$.
- Side branch at $1$: $F_4(z)=z$, so $G_1(z)=1+z$.
- Need $d-L=1$ extra vertex: coefficient of $z^1$ is $1$ → unique set $\{2,1,3,4\}$.
For $d=3$: need $z^0$ coefficient → only $1$ way without $4$ → $\{2,1,3\}$.
| Step | Cost |
|---|---|
| Find $P(a,b)$ | $O(n)$ |
| Side DP with size polynomials | Each conv_skip on polynomials of length $O(d)$ costs $O(d^2)$; at most $O(n)$ side roots and path attachment points |
| Final coefficient $[z^{d-L}]$ | $O(d)$ |
Time: $O(n d^2)$ per test case in the worst case (each conv_skip is $O(d^2)$, up to $O(n)$ side attachments). With $n, d \le 2000$ that is $O(8 \cdot 10^9)$ in theory, but on typical trees (long Steiner path, few side branches) it is much faster — often $O(n)$ after marking the core.
Space: $O(n + d)$.
$\sum n \le 2000$ caps the input size. E1 is one fixed pair per test, so you are not paying $O(n^2)$ unless you loop over all pairs yourself (E2 does, and needs $O(n^2)$).
| Version | Time per test | Notes |
|---|---|---|
| E0 | $O(n)$ | one fixed pair |
| E1 | $O(n d^2)$ worst case | one fixed pair |
| E1 + NTT | $O(n d \log d)$ worst case | atcoder::convolution on capped merges |
| Brute all pairs | $O(n^2 \cdot n d^2) = O(n^3 d^2)$ | run E1 for every pair — too slow |
| E2 | $O(n^2)$ | all pairs, distance only |
| Version | What you count |
|---|---|
| E1 (here) | Connected supersets of $\{a,b\}$ with $\|S\|=d$ |
| E2 | Pairs $(a,b)$ whose minimal connected subgraph has size $d$ → $ |
| E | Triples whose minimal (Steiner) subgraph has size $d$ — path case + three-branch case |
The polynomial / convolution machinery from E1 reappears in E when you track depths from a root instead of sizes from side branches.
Trick 7 connection (blog entry 100910)
Side-branch conv_skip merges children one at a time:
Each merge loops over indices of two vectors whose lengths are bounded by subtree size (or by $d$ after restricting to size $\le d$). That is exactly the sequential sibling merge from Trick 7 / Miss Punyverse: total work $O(n^2)$ on the side tree, or $O(n d)$ with the $O(nk)$ cap $k = d$.
See model solution.
Two further optimizations on top of the baseline DP:
-
Cap at $\mathrm{need} = d - L$. Side-branch
conv_skiponly needs degrees up to $\mathrm{need}$; truncate after every merge. -
Knapsack on the mandatory path. Instead of multiplying full polynomials along $P(a,b)$, keep $\mathrm{dp}[s]$ = number of ways to allocate exactly $s$ extra vertices from the processed prefix. Update with the capped branch polynomial at each path vertex — $O(L \cdot \mathrm{need}^2)$ instead of growing a degree-$(\mathrm{need})$ polynomial explicitly.
-
Small-to-large sibling merge. When a side vertex has several off-path children, merge their capped polynomials smallest first so intermediate vectors stay short (same Trick 7 idea as Miss Punyverse).
| Part | Bound |
|---|---|
| Side subtrees (capped merges, STL order) | $O(n \cdot \mathrm{need})$ amortized (Trick 7); $O(n \cdot \mathrm{need}^2)$ worst case per naive merge |
| Path knapsack | $O(L \cdot \mathrm{need}^2)$ |
| Total | $O(n \cdot \mathrm{need} + L \cdot \mathrm{need}^2)$; $O(n d^2)$ when $L = \Theta(n)$ and $\mathrm{need} = \Theta(d)$ |
With $\mathrm{need} \le d$, this is $O(n d + L d^2)$ per query — much better on typical trees when the path is short or $\mathrm{need} \ll d$, but still $O(n d^2)$ in the adversarial worst case (see NTT section below).
See the Knapsack on path + small-to-large block in code.
See the O(n d log d) via Atcoder NTT block in code for model_sol_ntt.cpp.
The $O(n d^2)$ worst case comes entirely from naive polynomial multiplication at degree $\mathrm{need} \le d$:
- Each capped
conv_skip_capcosts $O(\mathrm{need}^2)$. - There are $O(n)$ such merges over side edges plus $O(L)$ path knapsack updates, each $O(\mathrm{need}^2)$ when done naively.
Replace every multiply of two capped polynomials with Atcoder Library NTT (#include <atcoder/convolution>, atcoder::convolution on modint998244353 coefficients). A single multiply of degree $\le d$ then costs $O(d \log d)$.
If side polynomial $F$ has $F(0)=0$, then conv_skip(a, F) is multiplication by $(1+F(z))$. Build
and set nxt = convolution(a, c), then truncate to length $\mathrm{need}+1$. Same mod $998244353$ as the answer.
At path vertex $v$, instead of the double loop over $s,t$:
$$\mathrm{ndp} = \mathrm{convolution}(\mathrm{dp},\; G_v)\bmod z^{\mathrm{need}+1}$$(keep only the first $\mathrm{need}+1$ coefficients).
| Part | Naive (opt) | With atcoder::convolution |
|---|---|---|
| Side merges ($O(n)$) | $O(n \cdot d^2)$ worst case | $O(n \cdot d \log d)$ |
| Path ($L$ steps) | $O(L \cdot d^2)$ | $O(L \cdot d \log d)$ |
| Total | $O(n d^2)$ | $O(n d \log d)$ |
Since $L \le n$ and $\mathrm{need} \le d$, the whole test case is $O(n d \log d)$ in the worst case.
- Use the same
mint = modint998244353as the rest of the tutorial chain; ACL convolution expects that modulus. - NTT has larger constants than naive $O(d^2)$ for small degrees. A hybrid (naive below a threshold, NTT above) is fine for speed; all NTT is what gives the $O(n d \log d)$ worst-case proof.
- This does not beat $O(n d \log d)$ within the generating-function DP: you still perform $\Theta(n)$ convolutions of degree $\Theta(d)$ on adversarial trees (long path, every side branch a full degree-$d$ factor).
- The same idea applies to E4 (Steiner core instead of a path); E2 has no polynomial merges.