Skip to Content

Tours de Euler

Recursos

Recursos
FuenteRecursoNotas
CPH19.1 - Eulerian Tours
CP24.7.3 - Eulerian Graph
HechoFuenteNombreDificultadTagsSolución
CSESMail Delivery (Undirected)FácilEuler Touren el módulo

Explicación

Primero, definamos qué es un camino euleriano.

Un camino euleriano es un camino que recorre cada arista una vez.

De forma similar, un ciclo euleriano es un camino euleriano que empieza y termina en el mismo nodo.

Una condición importante es que un grafo puede tener un ciclo euleriano (¡no un camino!) si y solo si todo nodo tiene grado par.

Ahora, para hallar el ciclo euleriano ejecutamos un DFS modificado. El DFS solo recorre aristas no visitadas y la misma arista puede procesarse varias veces a lo largo del DFS, así que la eliminamos del grafo en la primera visita.

El algoritmo descrito es el algoritmo de Hierholzer .

Implementación

Complejidad temporal: O(E)\mathcal{O}(E)

#include <bits/stdc++.h> using namespace std; int n, m; vector<vector<pair<int, int>>> g; vector<int> path; vector<bool> seen; void dfs(int node) { while (!g[node].empty()) { auto [son, idx] = g[node].back(); g[node].pop_back(); if (seen[idx]) { continue; } seen[idx] = true; dfs(son); } path.push_back(node); } int main() { cin >> n >> m; vector<int> degree(n, 0); g.resize(n); degree.resize(n); seen.resize(m); for (int i = 0; i < m; i++) { int x, y; cin >> x >> y; x--, y--; g[x].emplace_back({y, i}); g[y].emplace_back({x, i}); degree[x]++; degree[y]++; } for (int node = 0; node < n; node++) { if (degree[node] % 2) { cout << "IMPOSSIBLE" << endl; return 0; } } dfs(0); if (path.size() != m + 1) { cout << "IMPOSSIBLE"; } else { for (int node : path) { cout << node + 1 << ' '; } } cout << endl; }
import java.io.*; import java.util.*; public class EulerianCycle { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int n = Integer.parseInt(st.nextToken()); int m = Integer.parseInt(st.nextToken()); List<int[]>[] g = new ArrayList[n]; for (int i = 0; i < n; i++) { g[i] = new ArrayList<>(); } boolean[] seen = new boolean[m]; int[] degree = new int[n]; for (int i = 0; i < m; i++) { st = new StringTokenizer(br.readLine()); int x = Integer.parseInt(st.nextToken()) - 1; int y = Integer.parseInt(st.nextToken()) - 1; g[x].add(new int[] {y, i}); g[y].add(new int[] {x, i}); degree[x]++; degree[y]++; } // Comprobar que todos los grados son pares for (int d : degree) { if ((d & 1) == 1) { System.out.println("IMPOSSIBLE"); return; } } ArrayDeque<Integer> stack = new ArrayDeque<>(); ArrayList<Integer> path = new ArrayList<>(); stack.push(0); while (!stack.isEmpty()) { int u = stack.peek(); while (!g[u].isEmpty() && seen[g[u].get(g[u].size() - 1)[1]]) { g[u].remove(g[u].size() - 1); // saltar aristas usadas } if (g[u].isEmpty()) { path.add(u); stack.pop(); } else { int[] edge = g[u].remove(g[u].size() - 1); int v = edge[0], idx = edge[1]; if (!seen[idx]) { seen[idx] = true; stack.push(v); } } } PrintWriter pw = new PrintWriter(System.out); if (path.size() != m + 1) { pw.println("IMPOSSIBLE"); } else { for (int i = path.size() - 1; i >= 0; i--) { pw.print((path.get(i) + 1) + " "); } pw.println(); } pw.close(); } }
n, m = map(int, input().split()) g = [[] for _ in range(n)] seen = [0] * m degree = [0] * n for i in range(m): x, y = [int(j) - 1 for j in input().split()] g[x].append((y, i)) g[y].append((x, i)) degree[x] += 1 degree[y] += 1 for node in range(n): if degree[node] % 2 == 1: print("IMPOSSIBLE") exit() path = [] stack = [0] while stack: u = stack[-1] if g[u]: son, idx = g[u].pop() if not seen[idx]: seen[idx] = 1 stack.append(son) else: path.append(stack.pop()) if len(path) != m + 1: print("IMPOSSIBLE") else: print(*[i + 1 for i in path])
HechoFuenteNombreDificultadTagsSolución
CSESTeleporters (Directed)FácilEuler Touren el módulo

Explicación

La condición de existencia de un camino euleriano en un grafo dirigido es: a lo sumo un nodo tiene outiini=1out_i - in_i=1 y a lo sumo un nodo tiene iniouti=1in_i - out_i=1. Esta propiedad se debe a que un camino o ciclo euleriano sale de un nodo el mismo número de veces que entra. En un grafo dirigido la excepción son el nodo de inicio y el nodo de fin.

Implementación

Complejidad temporal: O(E)\mathcal{O}(E)

#include <bits/stdc++.h> using namespace std; int n, m; vector<vector<int>> g; vector<int> in, out, path; void dfs(int node) { while (!g[node].empty()) { int son = g[node].back(); g[node].pop_back(); dfs(son); } path.push_back(node); } int main() { cin >> n >> m; g.resize(n + 1); in.resize(n + 1); out.resize(n + 1); for (int i = 0; i < m; i++) { int x, y; cin >> x >> y; g[x].push_back(y); out[x]++; in[y]++; } bool flag = true; for (int node = 2; node < n && flag; node++) { if (in[node] != out[node]) { flag = false; } } if (out[1] != in[1] + 1 || out[n] != in[n] - 1 || !flag) { cout << "IMPOSSIBLE"; return 0; } dfs(1); reverse(path.begin(), path.end()); if (path.size() != m + 1 || path.back() != n) { cout << "IMPOSSIBLE"; } else { for (auto node : path) { cout << node << ' '; } } }
path = [] def dfs(start: int): stack = [start] while stack: node = stack[-1] if g[node]: son = g[node].pop() stack.append(son) else: path.append(stack.pop()) n, m = map(int, input().split()) g = [[] for _ in range(n + 1)] in_degree = [0] * (n + 1) out_degree = [0] * (n + 1) for _ in range(m): x, y = map(int, input().split()) g[x].append(y) out_degree[x] += 1 in_degree[y] += 1 flag = True for node in range(2, n): if in_degree[node] != out_degree[node]: flag = False break if out_degree[1] != in_degree[1] + 1 or out_degree[n] != in_degree[n] - 1 or not flag: print("IMPOSSIBLE") exit() dfs(1) path.reverse() if len(path) != m + 1 or path[-1] != n: print("IMPOSSIBLE") else: print(*path)

Secuencias de De Bruijn

HechoFuenteNombreDificultadTagsSolución
CSESDe Bruijn SequenceFácilHamiltonian pathen el módulo

Una secuencia de De Bruijn  es una cadena de longitud mínima que contiene cada string de longitud nn exactamente una vez como subcadena, para un alfabeto fijo con kk letras. En nuestro caso k=2k=2 porque solo tenemos 00 y 11.

Veamos algunos casos particulares:

  1. n=2n=2 \rightarrow 00110
  2. n=3n=3 \rightarrow 0001011100

Podemos visualizar las transiciones — agregar 00 o 11 — usando un grafo orientado cuyos nodos contienen un string de longitud n1n-1.

de-bruijn Cómo se ve el grafo para n=3n=3

Un camino euleriano en el grafo de arriba representa una solución válida. El nodo de partida tiene n1n-1 caracteres y hay knk^n aristas que cada una agrega un carácter más, así que la longitud de un string de De Bruijn es kn+n1k^n+n-1.

Implementación

Complejidad temporal: O(2N)\mathcal{O}(2^N)

#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; if (n == 1) { cout << "10" << endl; return 0; } vector<vector<int>> adj(1 << (n - 1)); for (int node = 0; node < (1 << (n - 1)); node++) { int son = (node << 1) % (1 << (n - 1)); adj[node].push_back(son); adj[node].push_back(son | 1); } stack<int> todo; todo.push(0); vector<int> path; while (!todo.empty()) { int node = todo.top(); if (!adj[node].empty()) { todo.push(adj[node].back()); adj[node].pop_back(); } else { path.push_back(node & 1); todo.pop(); } } for (int i = 0; i < n - 2; i++) { path.push_back(0); } for (int digit : path) { cout << digit; } cout << endl; }
n = int(input()) if n == 1: print("10") exit() adj = [[] for _ in range(1 << (n - 1))] for node in range(1 << (n - 1)): son = (node << 1) % (1 << (n - 1)) adj[node].append(son) adj[node].append(son | 1) todo = [0] path = [] while todo: node = todo[-1] if adj[node]: todo.append(adj[node][-1]) adj[node].pop() else: path.append(node & 1) todo.pop() path = path + [0] * (n - 2) print("".join(map(str, path)))

Problemas

HechoFuenteNombreDificultadTagsSolución
Baltic OI2014 - PostmenFácilEuler TourSolución
CFTanya and PasswordFácil
CSAMatching SubstringsNormal
CFTurtle and MultiplicationNormal
CFJohnny and Megan's NecklaceNormalEuler Tour
CFData Center DramaNormalEuler Tour
Balkan OI2016 - AcrobatNormalEuler TourSolución