CF 104090G - Subgraph Isomorphism

We are given a connected undirected simple graph $G$. From this graph, consider all connected subgraphs that use all $n$ vertices and contain exactly $n-1$ edges.

CF 104090G - Subgraph Isomorphism

Rating: -
Tags: -
Solve time: 57s
Verified: yes

Solution

Problem Understanding

We are given a connected undirected simple graph $G$. From this graph, consider all connected subgraphs that use all $n$ vertices and contain exactly $n-1$ edges. Any such subgraph is necessarily a spanning tree of $G$, because it is connected, acyclic (since edges equal $n-1$), and spans all vertices.

The question asks whether there exists a tree $T$ such that every spanning tree of $G$ is structurally identical to $T$ up to relabeling of vertices. In other words, no matter which spanning tree you pick from $G$, all of them must be isomorphic as unrooted trees.

The output is a simple decision per test case, “YES” if all spanning trees of the given graph are isomorphic to each other, and “NO” otherwise.

The constraints allow up to $10^5$ test cases with total $n$ and $m$ across tests up to $10^6$. That forces a linear or near-linear solution per test case. Any approach that enumerates spanning trees or performs heavy combinatorial reasoning per edge selection is immediately impossible, since even a single graph can have exponentially many spanning trees.

A naive attempt would be to generate multiple spanning trees (for example by removing different edges in DFS spanning trees) and check graph isomorphism between them. Graph isomorphism even for trees can be checked in linear time, but the number of spanning trees in a dense graph is still exponential, so this approach breaks immediately.

Another naive direction is to assume that “small cycle changes” do not affect structure, but that intuition fails in graphs where branching exists. For instance, in a graph with a cycle and a leaf attached, different choices of removed cycle edge can shift where the branching sits in the resulting tree, and that can change the tree shape in a non-isomorphic way if more complex branching exists elsewhere.

Approaches

A first observation is that we are not comparing arbitrary subgraphs, but only spanning trees. The structure of spanning trees is controlled entirely by the cycle space of the graph. Every extra edge beyond a tree introduces at least one cycle, and each cycle introduces flexibility: removing different cycle edges yields different spanning trees.

If the graph is already a tree, then there is exactly one spanning tree, so the condition holds trivially.

If the graph contains exactly one cycle, then every spanning tree is formed by deleting exactly one edge from that cycle. The rest of the structure remains fixed. This produces a family of trees that differ only in which edge of a single cycle was removed. Since the cycle can be “opened” at any position into a path, all resulting trees are isomorphic: the cycle becomes a path, and all attachments to cycle vertices remain attached in corresponding positions along that path up to relabeling.

The difficulty starts when the graph contains more than one independent cycle. With two cycles, there are multiple independent choices of edges to delete, and different combinations can change global branching patterns. This typically produces spanning trees with different degree distributions or different arrangements of branching points along paths, which cannot be reconciled by any isomorphism.

A useful way to see the boundary is through edge count. A connected graph with $n-1$ edges is a tree. With $n$ edges, it is unicyclic. With more than $n$ edges, it has at least two cycles, and this is exactly where non-isomorphic spanning trees start appearing.

Thus the condition reduces to checking whether the graph is either a tree or a simple cycle.

Approach Time Complexity Space Complexity Verdict
Brute force spanning tree generation + isomorphism checks Exponential High Too slow
Degree and cycle count characterization $O(n + m)$ $O(n + m)$ Accepted

Algorithm Walkthrough

The key idea is to classify the graph by how many “extra” edges it has beyond a tree, and whether that extra structure forms exactly one simple cycle.

Steps

  1. Compute the number of edges $m$ and vertices $n$. If $m = n - 1$, the graph is already a tree, so there is only one spanning tree. We can immediately output “YES”.
  2. If $m = n$, the graph has exactly one extra edge beyond a tree structure, which means it is unicyclic. In this case, verify that every vertex has degree exactly 2. This condition ensures the graph is a simple cycle without any attached branches.
  3. If the graph is a simple cycle, then removing any edge always produces a path on $n$ vertices. Since all spanning trees are paths of the same size, they are all isomorphic, so we output “YES”.
  4. In all other cases, the graph has either branching structure or multiple cycles. That guarantees the existence of at least two spanning trees with different structural properties, so we output “NO”.

Why it works

A tree has exactly one spanning tree, so it trivially satisfies the condition. A simple cycle is the only connected graph with exactly one cycle and no branching, and in that case all spanning trees are identical paths up to relabeling. The presence of any additional cycle or any vertex of degree not compatible with a cycle introduces structural asymmetry in spanning trees, allowing at least two non-isomorphic outcomes. This divides all graphs cleanly into “at most one cycle and uniform structure” versus “structurally diverse spanning trees”.

Python Solution

import sys
input = sys.stdin.readline

def solve():
    T = int(input())
    out = []

    for _ in range(T):
        n, m = map(int, input().split())
        deg = [0] * (n + 1)

        for _ in range(m):
            u, v = map(int, input().split())
            deg[u] += 1
            deg[v] += 1

        if m == n - 1:
            out.append("YES")
        elif m == n:
            ok = True
            for i in range(1, n + 1):
                if deg[i] != 2:
                    ok = False
                    break
            out.append("YES" if ok else "NO")
        else:
            out.append("NO")

    print("\n".join(out))

if __name__ == "__main__":
    solve()

The implementation relies only on degree counting and edge counting per test case. The key decision point is distinguishing trees and simple cycles. The degree check ensures that the unicyclic case is a pure cycle rather than a cycle with attached trees.

A subtle point is that the condition $m = n$ alone is not sufficient. A graph with $n$ edges can still have branching if a cycle exists with trees attached to it. Those attachments create vertices with degree greater than 2 or leaves, and that immediately breaks the uniform cycle structure required for all spanning trees to remain isomorphic.

Worked Examples

Example 1

Input graph is a cycle on 5 vertices.

We track degrees and classification:

Step n m Degree condition Decision
Input 5 5 all vertices degree 2 YES

Removing any edge yields a path of 5 nodes. Every resulting tree is a path, so all spanning trees are isomorphic.

Example 2

Input graph is a triangle with an extra leaf attached.

Step n m Degree condition Decision
Input 4 4 vertex degrees not all 2 NO

Here, spanning trees depend on which cycle edge is removed. In some cases the leaf attaches to an internal node of the resulting path, and in others it attaches closer to an endpoint, producing non-isomorphic trees.

This shows that even a single attachment to a cycle breaks the uniform structure of spanning trees.

Complexity Analysis

Measure Complexity Explanation
Time $O(n + m)$ per test Each edge is read once and contributes to degree counts
Space $O(n)$ Degree array for each test case

The total input size across all test cases is bounded by $10^6$, so this linear scanning approach easily fits within the time limit.

Test Cases

import sys, io

def run(inp: str) -> str:
    sys.stdin = io.StringIO(inp)
    from collections import deque
    input_data = sys.stdin.read().strip().split()
    it = iter(input_data)

    T = int(next(it))
    out = []

    for _ in range(T):
        n = int(next(it)); m = int(next(it))
        deg = [0] * (n + 1)
        for _ in range(m):
            u = int(next(it)); v = int(next(it))
            deg[u] += 1
            deg[v] += 1

        if m == n - 1:
            out.append("YES")
        elif m == n:
            ok = all(deg[i] == 2 for i in range(1, n + 1))
            out.append("YES" if ok else "NO")
        else:
            out.append("NO")

    return "\n".join(out)

# provided sample (conceptual, since formatting omitted in statement)
assert run("""4
7 6
1 2
2 3
3 4
4 5
5 6
3 7
3 3
1 2
2 3
3 1
5 5
1 2
2 3
3 4
4 1
1 5
1 0
""") == "YES\nYES\nNO\nYES"

# minimum tree
assert run("""1
1 0
""") == "YES"

# simple cycle
assert run("""1
4 4
1 2
2 3
3 4
4 1
""") == "YES"

# cycle with a leaf
assert run("""1
5 5
1 2
2 3
3 4
4 1
1 5
""") == "NO"
Test input Expected output What it validates
single vertex YES trivial tree case
pure cycle YES uniform spanning trees
cycle + leaf NO branching breaks isomorphism
sample mix YES/NO combined correctness

Edge Cases

A single vertex graph has no edges and exactly one spanning tree, so it must return “YES”. The algorithm classifies it as $m = n - 1$, which immediately passes.

A pure cycle is the canonical positive case for $m = n$. Each vertex has degree 2, so the degree check succeeds and the output is “YES”. Every spanning tree is a path, so no structural variation exists.

A cycle with a single extra leaf introduces a vertex of degree 3. The algorithm detects this during the degree scan and rejects it. The spanning trees differ depending on which cycle edge is removed, producing different attachment positions for the leaf along the resulting path, which confirms non-isomorphism.

Graphs with multiple cycles necessarily have $m > n$, and are rejected immediately. In such graphs, different spanning trees can differ in branching structure because cycles interact independently, producing non-isomorphic outcomes.