## 1. Abridged problem statement

You are given a convex polyhedron by listing all of its facets. Each facet is given as a cyclic list of its vertices' 3D coordinates. Equal vertices have exactly equal printed coordinates. The total number of distinct vertices is at most 100.

The sum of solid angles of the polyhedron is guaranteed to be less than `4` steradians.

Find a Hamiltonian cycle in the edge graph of the polyhedron: a cycle that visits every vertex exactly once and uses only polyhedron edges. If such a cycle exists, print `Yes`, then the vertices of one such cycle in order using the original coordinates. Otherwise print `No`.

---

## 2. Detailed editorial

### Step 1: Convert the polyhedron into a graph

The input gives facets as cyclic lists of vertices. Every pair of consecutive vertices on a facet boundary forms an edge of the polyhedron. The last vertex of the facet is also connected to the first one.

So we:

1. Read every vertex coordinate.
2. Identify equal vertices.
3. Assign each distinct vertex an integer id.
4. For every facet, add edges between consecutive vertex ids.

Because the same geometric vertex may appear in several facets, we need a way to recognize it. The solution converts each coordinate to an integer number of nanounits:

```cpp
llround(stod(s[j]) * 1e9)
```

Since coordinates are printed with exactly 9 digits after the decimal point, this uniquely represents the printed coordinate.

After reading all facets, we have an undirected graph with at most 100 vertices.

---

### Step 2: Why a Hamiltonian cycle exists

The condition about the sum of solid angles being less than `4` steradians is not used computationally.

A theorem by Barnette implies that, for convex 3-dimensional polyhedra, sufficiently small total solid angle forces the edge graph to be Hamiltonian. In particular, under the promise in this problem, the required Hamiltonian cycle is guaranteed to exist.

Therefore, the main task is to construct one.

---

### Step 3: Backtracking search

The solution searches for a Hamiltonian cycle using DFS/backtracking.

We fix vertex `0` as the start of the cycle. Then we build a path:

```text
path[0], path[1], ..., path[depth - 1]
```

where `path[0] = 0`.

At each step, the current endpoint is `cur = path[depth - 1]`. We try to extend the path to an unvisited neighbor of `cur`. If all vertices have been visited, we only need to check whether the last vertex is adjacent to the start vertex. If yes, we found a Hamiltonian cycle.

---

### Step 4: Pruning rule 1 — every remaining vertex needs degree at least 2

In a Hamiltonian cycle, every vertex has exactly two cycle-neighbors. For an unvisited vertex, its future two neighbors can only be other unvisited vertices, the current path endpoint `cur`, or the start vertex `path[0]`. It cannot connect to an already visited internal path vertex.

So for every unvisited vertex `u`, the algorithm counts how many neighbors are still usable:

```cpp
if (!visited[nb] || nb == cur || nb == path[0])
    avail++;
```

If some unvisited vertex has fewer than two usable neighbors, the current branch cannot lead to a Hamiltonian cycle and is abandoned.

---

### Step 5: Pruning rule 2 — remaining vertices must be reachable

The path can only grow from the current endpoint `cur`. Therefore, all still-unvisited vertices must be reachable from `cur` through unvisited vertices. The algorithm performs a DFS/BFS from all unvisited neighbors of `cur`, walking only through unvisited vertices. If it cannot reach every unvisited vertex, the branch is impossible.

---

### Step 6: Candidate ordering heuristic

The search tries next vertices in increasing order of their number of unvisited neighbors. This is similar to Warnsdorff's rule for the knight's tour problem: visit the most constrained vertices first. It greatly reduces backtracking in practice.

---

### Complexity

Hamiltonian cycle search is NP-hard in general, so this backtracking algorithm has exponential worst-case complexity. However, the input graphs come from convex polyhedra satisfying a strong geometric promise, and the number of vertices is at most 100. The pruning and heuristic make the search fast enough for the intended test data.

---

## 3. C++ Solution

```cpp
#include <bits/stdc++.h>

using namespace std;

template<typename T1, typename T2>
ostream& operator<<(ostream& out, const pair<T1, T2>& x) {
    return out << x.first << ' ' << x.second;
}

template<typename T1, typename T2>
istream& operator>>(istream& in, pair<T1, T2>& x) {
    return in >> x.first >> x.second;
}

template<typename T>
istream& operator>>(istream& in, vector<T>& a) {
    for(auto& x: a) {
        in >> x;
    }
    return in;
};

template<typename T>
ostream& operator<<(ostream& out, const vector<T>& a) {
    for(auto x: a) {
        out << x << ' ';
    }
    return out;
};

int n = 0;
vector<array<string, 3>> vertex_coord;
vector<vector<int>> adj;
vector<vector<char>> connected;
map<array<int64_t, 3>, int> vertex_id;
vector<char> visited;
vector<int> path;

void read() {
    int f;
    cin >> f;

    set<pair<int, int>> edges;
    for(int facet = 0; facet < f; facet++) {
        int k;
        cin >> k;

        vector<int> ids(k);
        for(int i = 0; i < k; i++) {
            array<string, 3> s;
            cin >> s[0] >> s[1] >> s[2];

            array<int64_t, 3> key;
            for(int j = 0; j < 3; j++) {
                key[j] = llround(stod(s[j]) * 1e9);
            }

            auto it = vertex_id.find(key);
            if(it == vertex_id.end()) {
                ids[i] = n++;
                vertex_id[key] = ids[i];
                vertex_coord.push_back(s);
            } else {
                ids[i] = it->second;
            }
        }

        for(int i = 0; i < k; i++) {
            int a = ids[i], b = ids[(i + 1) % k];
            if(a > b) {
                swap(a, b);
            }
            edges.insert({a, b});
        }
    }

    adj.assign(n, {});
    connected.assign(n, vector<char>(n, 0));
    for(auto& e: edges) {
        adj[e.first].push_back(e.second);
        adj[e.second].push_back(e.first);
        connected[e.first][e.second] = 1;
        connected[e.second][e.first] = 1;
    }
}

bool dfs(int cur, int depth) {
    if(depth == n) {
        return connected[cur][path[0]];
    }

    for(int u = 0; u < n; u++) {
        if(visited[u]) {
            continue;
        }

        int avail = 0;
        for(int nb: adj[u]) {
            if(!visited[nb] || nb == cur || nb == path[0]) {
                avail++;
            }
        }

        if(avail < 2) {
            return false;
        }
    }

    int target = n - depth;
    vector<char> seen(n, 0);
    vector<int> reach;
    for(int nb: adj[cur]) {
        if(!visited[nb] && !seen[nb]) {
            seen[nb] = 1;
            reach.push_back(nb);
        }
    }

    int cnt = reach.size();
    while(!reach.empty()) {
        int x = reach.back();
        reach.pop_back();
        for(int nb: adj[x]) {
            if(!visited[nb] && !seen[nb]) {
                seen[nb] = 1;
                cnt++;
                reach.push_back(nb);
            }
        }
    }

    if(cnt != target) {
        return false;
    }

    vector<pair<int, int>> cand;
    for(int nb: adj[cur]) {
        if(visited[nb]) {
            continue;
        }

        int deg = 0;
        for(int x: adj[nb]) {
            if(!visited[x]) {
                deg++;
            }
        }
        cand.push_back({deg, nb});
    }

    sort(cand.begin(), cand.end());
    for(auto& c: cand) {
        int nb = c.second;
        visited[nb] = 1;
        path[depth] = nb;
        if(dfs(nb, depth + 1)) {
            return true;
        }
        visited[nb] = 0;
    }

    return false;
}

void solve() {
    // Geometry to graph: the facets arrive as cyclic vertex lists, so the edge
    // graph is rebuilt by identifying vertices with equal coordinates (rounded
    // to the 9 printed digits, safe because distinct vertices lie at least
    // 5e-7 apart) and joining every facet's consecutive vertices; an edge that
    // borders two facets is simply encountered twice. By Steinitz's theorem
    // this graph is planar and 3-connected, i.e. a genuine polyhedral graph.
    //
    // The solid angle at a vertex is the area its incident faces carve out of a
    // unit sphere centred there. For a vertex of degree d it equals the
    // spherical-polygon excess (the sum of the dihedral angles around the
    // vertex) - (d - 2)*pi, and always lies strictly between 0 and 2*pi.
    // Summing this over all vertices gives S(P), the total solid angle, with a
    // full sphere measuring 4*pi steradians.
    //
    // Barnette's theorem ties S(P) to Hamiltonicity: a 3-polytope's
    // combinatorial type can be realised by polyhedra whose total solid angle
    // is arbitrarily close to 0 exactly when its edge graph has a Hamiltonian
    // cycle, while every non-Hamiltonian type keeps S(P) bounded away from 0.
    // The intuition for one direction is that a Hamiltonian cycle lets one
    // flatten the polyhedron towards that cycle, driving every vertex's solid
    // angle to 0, which no non-Hamiltonian type permits. The problem promises
    // S(P) < 4 steradians, which sits under that non-Hamiltonian bound, so the
    // graph is guaranteed Hamiltonian and the answer is always "Yes".
    // Grinberg's theorem gives a separate, purely combinatorial necessary
    // condition for planar Hamiltonicity but it is not needed here.
    //
    // Constructing the cycle is the only real work, and a guaranteed-polynomial
    // route exists in special cases: a 4-connected planar graph is Hamiltonian
    // and Chiba-Nishizeki finds a cycle in linear time. But small-solid-angle
    // polyhedra need not be 4-connected (a thin prism is only 3-connected), and
    // turning Barnette's "flatten towards the cycle" argument into a direct
    // construction is fiddly, so with at most 100 vertices we instead opt for a
    // heuristic search; the guaranteed existence of a cycle keeps it fast in
    // practice on these instances, though it carries no worst-case bound.
    //
    // The search is a backtracking walk from vertex 0. At each step it extends
    // the path to the unvisited neighbour that itself has the fewest unvisited
    // neighbours (Warnsdorff's rule), which heads into the cramped corners of
    // the graph first and so rarely has to backtrack. A branch is dropped the
    // moment it becomes unsatisfiable: some unvisited vertex is left with fewer
    // than two usable incident edges (it could never get both a predecessor and
    // a successor), or the unvisited vertices stop being reachable from the
    // current endpoint. The path closes into a cycle once all n vertices are
    // used and the final one is adjacent to vertex 0.
    //
    // Some references are:
    // - D. Barnette, "The sum of the solid angles of a d-polytope",
    //   Geometriae Dedicata 1 (1972), 100-102, doi:10.1007/BF00147383.
    // - E. Ja. Grinberg (1968), "Plane homogeneous graphs of degree three
    //   without Hamiltonian circuits"; English translation by D. Zeps,
    //   arXiv:0908.2563.

    visited.assign(n, 0);
    path.assign(n, 0);
    visited[0] = 1;
    path[0] = 0;

    if(dfs(0, 1)) {
        cout << "Yes\n" << n << "\n";
        for(int i = 0; i < n; i++) {
            auto& c = vertex_coord[path[i]];
            cout << c[0] << ' ' << c[1] << ' ' << c[2] << '\n';
        }
    } else {
        cout << "No\n";
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int T = 1;
    // cin >> T;
    for(int test = 1; test <= T; test++) {
        read();
        solve();
    }

    return 0;
}
```

---

## 4. Python Solution

```python
import sys

# Increase recursion limit because the Hamiltonian path can have up to 100 vertices.
sys.setrecursionlimit(10000)


def solve():
    # Read all input tokens.
    data = sys.stdin.read().split()

    # Pointer into token list.
    ptr = 0

    # Number of facets.
    f = int(data[ptr])
    ptr += 1

    # Maps coordinate triples to vertex ids.
    # Coordinates are kept as strings because equal vertices are printed identically.
    vertex_id = {}

    # vertex_coord[id] stores the original coordinate strings of that vertex.
    vertex_coord = []

    # Set of undirected edges, stored as pairs (min_id, max_id).
    edges = set()

    # Process each facet.
    for _ in range(f):
        # Number of vertices in this facet.
        k = int(data[ptr])
        ptr += 1

        # ids[i] will be the graph id of the i-th vertex of the facet.
        ids = []

        # Read all vertices of this facet.
        for _ in range(k):
            # Read coordinates as strings to preserve exact output formatting.
            x = data[ptr]
            y = data[ptr + 1]
            z = data[ptr + 2]
            ptr += 3

            # Coordinate triple key.
            key = (x, y, z)

            # If this vertex was not seen before, assign it a new id.
            if key not in vertex_id:
                vertex_id[key] = len(vertex_coord)
                vertex_coord.append(key)

            # Store the id of this facet vertex.
            ids.append(vertex_id[key])

        # Add edges between consecutive vertices of the facet boundary.
        for i in range(k):
            a = ids[i]
            b = ids[(i + 1) % k]

            # Normalize undirected edge order.
            if a > b:
                a, b = b, a

            # Add to set to remove duplicates.
            edges.add((a, b))

    # Number of distinct vertices.
    n = len(vertex_coord)

    # Build adjacency list.
    adj = [[] for _ in range(n)]

    # Build adjacency matrix for O(1) edge existence checks.
    connected = [[False] * n for _ in range(n)]

    # Fill graph structures.
    for a, b in edges:
        adj[a].append(b)
        adj[b].append(a)
        connected[a][b] = True
        connected[b][a] = True

    # visited[v] is True if v is already in current path.
    visited = [False] * n

    # path[i] is the i-th vertex in current path.
    path = [0] * n

    # Start Hamiltonian path from vertex 0.
    visited[0] = True
    path[0] = 0

    def dfs(cur, depth):
        # If all vertices are used, check whether the path closes into a cycle.
        if depth == n:
            return connected[cur][path[0]]

        # Pruning 1: Every unvisited vertex needs at least two possible neighbors
        # in the final Hamiltonian cycle.
        for u in range(n):
            if visited[u]:
                continue

            avail = 0
            for nb in adj[u]:
                if (not visited[nb]) or nb == cur or nb == path[0]:
                    avail += 1

            if avail < 2:
                return False

        # Pruning 2: All unvisited vertices must be reachable from cur through
        # unvisited vertices.
        target = n - depth
        seen = [False] * n
        stack = []

        for nb in adj[cur]:
            if not visited[nb] and not seen[nb]:
                seen[nb] = True
                stack.append(nb)

        cnt = len(stack)

        while stack:
            x = stack.pop()
            for nb in adj[x]:
                if not visited[nb] and not seen[nb]:
                    seen[nb] = True
                    cnt += 1
                    stack.append(nb)

        if cnt != target:
            return False

        # Candidate next vertices sorted by number of unvisited neighbors.
        candidates = []
        for nb in adj[cur]:
            if visited[nb]:
                continue
            deg = 0
            for x in adj[nb]:
                if not visited[x]:
                    deg += 1
            candidates.append((deg, nb))

        candidates.sort()

        for _, nb in candidates:
            visited[nb] = True
            path[depth] = nb
            if dfs(nb, depth + 1):
                return True
            visited[nb] = False

        return False

    # Run DFS.
    found = dfs(0, 1)

    # Prepare output.
    out = []

    if found:
        out.append("Yes")
        out.append(str(n))

        # Print vertices in Hamiltonian cycle order.
        for v in path:
            x, y, z = vertex_coord[v]
            out.append(f"{x} {y} {z}")
    else:
        out.append("No")

    # Write final answer.
    sys.stdout.write("\n".join(out))


if __name__ == "__main__":
    solve()
```

---

## 5. Compressed editorial

Build the edge graph of the polyhedron. Assign an id to every distinct coordinate triple. For each facet, connect consecutive vertices, including the last to the first. Duplicate edges are ignored.

The solid-angle condition guarantees, by Barnette's theorem, that the graph has a Hamiltonian cycle. The solution therefore searches for one using backtracking.

Start from vertex `0` and recursively extend a simple path. When all vertices are used, check whether the last vertex is adjacent to the start.

Use two pruning rules:

1. Every unvisited vertex must still have at least two usable neighbors: unvisited vertices, the current endpoint, or the start vertex.
2. All unvisited vertices must be reachable from the current endpoint through unvisited vertices.

Try next vertices in increasing order of their number of unvisited neighbors, which explores constrained choices first.

If DFS succeeds, output the vertices of the found cycle in order using their original coordinate strings; otherwise output `No`.
