DSA sheet / Graph

Round Trip

medium about 30 min BFS / DFScycle-detectionbfs-treeundirected-graph
Open on CSES

Do these first: Message Route

The problem in brief

There are n cities (up to 100 000) and m two-way roads (up to 200 000), with at most one road between any two cities. A round trip starts at a city, goes through at least three different cities using roads, and returns to the start without visiting any other city twice. Print the number of cities in the printed route (counting the start twice) and one such route, or IMPOSSIBLE if there is no round trip. Any valid answer is accepted.

Try it

Tab indents; press Esc, then Tab, to leave the box. Ctrl+Enter runs.

Hints

Stuck? Reveal one hint at a time. Each nudges without giving away the next.

The walkthrough

Spoilers ahead: open a section only after you have given the hints a fair try.

How to think

A forest (every component a tree) has no cycles, and any extra edge inside a component closes one. So the question is: is there an edge that does not belong to the traversal forest, and if so, what cycle does it make? Traverse with BFS (or DFS) recording parent and depth. The first time you see an edge from u to an already visited v that is not u’s parent, you have a non-tree edge. The tree path from u to v plus that edge is a cycle, and because the graph has no multi-edges the tree path has at least two edges, so the cycle has at least three cities.

To recover the tree path, climb from the deeper endpoint upward until depths match, then climb both together until the two pointers meet at the lowest common ancestor. Concatenate the two climbs. The habit: prove a structural fact first (extra edge means cycle), then use the cheapest extraction that the traversal already supports (parent pointers plus depth), instead of thinking about cycles in general.

Intuition

Explore the roads like spreading ink from a city. If the ink ever reaches a city it has already coloured by a road other than the one it just travelled, two ink paths have met, and the loop formed by walking back along both paths to their common origin is a round trip.

Approach
  1. Build adjacency lists.
  2. For every unvisited city, BFS from it with parent[] and depth[]. For each neighbour v of u: if v is unvisited, set parent[v] = u, depth[v] = depth[u] + 1 and push it; else if v != parent[u], you found a non-tree edge (u, v): stop.
  3. Build the cycle: climb from u and from v to their lowest common ancestor, collect both climbs, then output u … lca … v followed by u again.
  4. If no such edge is found print IMPOSSIBLE.

Pitfalls: the graph may be disconnected, so start a BFS from every unvisited city; skip the edge back to the parent; use iterative BFS (deep recursion is unsafe at 10^5 nodes); the printed count includes the repeated start city, so it is the number of distinct cities plus one.

Complexity

O(n + m) time and memory.

Solutions

Written from scratch and checked by compiling and running each one against a brute force on random inputs. Fast input/output, the way you would submit it.

Show solutions (C++17, Python 3, Java 17, Node.js)
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> adj(n + 1);
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        adj[a].push_back(b);
        adj[b].push_back(a);
    }
    vector<int> parent(n + 1, 0), depth(n + 1, 0);
    vector<char> seen(n + 1, 0);
    for (int s = 1; s <= n; s++) {
        if (seen[s]) continue;
        seen[s] = 1;
        vector<int> queue = {s};
        for (size_t head = 0; head < queue.size(); head++) {
            int u = queue[head];
            for (int v : adj[u]) {
                if (!seen[v]) {
                    seen[v] = 1;
                    parent[v] = u;
                    depth[v] = depth[u] + 1;
                    queue.push_back(v);
                } else if (v != parent[u]) {
                    // Non-tree edge (u, v): tree path u..lca..v plus this edge is a cycle.
                    vector<int> left, right;
                    int a = u, b = v;
                    while (depth[a] > depth[b]) { left.push_back(a); a = parent[a]; }
                    while (depth[b] > depth[a]) { right.push_back(b); b = parent[b]; }
                    while (a != b) {
                        left.push_back(a); right.push_back(b);
                        a = parent[a]; b = parent[b];
                    }
                    left.push_back(a);
                    reverse(right.begin(), right.end());
                    left.insert(left.end(), right.begin(), right.end());
                    left.push_back(u);
                    cout << left.size() << "\n";
                    for (size_t i = 0; i < left.size(); i++) cout << left[i] << (i + 1 < left.size() ? ' ' : '\n');
                    return 0;
                }
            }
        }
    }
    cout << "IMPOSSIBLE\n";
    return 0;
}

Problem: CSES Problem Set (Antti Laaksonen, University of Helsinki), CC BY-NC-SA 4.0. The explanations and code above are original (© Anupam Kumar).

Related problems

Back to the sheet