UVA - 11709 - Trust groups![]()
This article will explain my thought process to solve a problem called Trust groups (PDF) from UVA Online Judge. Read the original problem statement before reading this article.
Keywords: Graph, Strongly Connected Components
Problem Summary![]()
Given:
List of peoples
List of trusts between each peoples
We want to:
Find the minimum number of stable groups of peoples
Glossary![]()
Graph — A structure consisting of a set of objects where some pairs of the objects are related.
Strongly Connected Components (SCC) — A subset of vertices where every vertex in the subset is reachable from every other vertex in the same subset by traversing the directed edges.
Kosaraju — An algorithm to find all strongly connected components in a directed graph.
Depth First Search (DFS) — A graph traversal algorithm that starts from a source vertex and explores each path completely before backtracking and exploring other paths.
Observation![]()
We could model the relationship between peoples and trusts as a directed graph, where the peoples are the vertices and the edges. Trust can be propagated, meaning that each peoples inherit the trust of its trusted peoples.
Stable group is formed when its members are trusted by each other, or in another words, if there is a bi-directional path between those peoples. This is similar to the concept of SCC, where the vertices are directly or indirectly connected to each other. The number of stable group should be equals to the number of SCC of the graph.
Algorithm![]()
One of the strategy to find SCC in a graph is by using Kosaraju algorithm. This is done by (1) creating 2 version of graph (normal and transposed), and (2) doing a 2-phase DFS on both graph. By taking advantage of the DFS traversal sequence, Kosaraju is able to retrieve every possible SCC in a graph.
The time complexity for this solution is O({total persons} + {total trusts}) or O(10^6).
Implementation![]()
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 11709 - Trust groups
* Time limit: 5.000 seconds
* https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=2756
*/
public class Main {
public static void main(final String... args) throws IOException {
final BufferedIO io = new BufferedIO(System.in, System.out);
final Process process = new Process();
while (true) {
final Input input = new Input();
final String[] l1 = io.readLine(" ");
input.totalPersons = Integer.parseInt(l1[0]);
input.totalTrusts = Integer.parseInt(l1[1]);
if (input.isEOF()) break;
input.persons = new String[input.totalPersons];
for (int i = 0; i < input.totalPersons; i++) {
input.persons[i] = io.readLine();
}
input.trusts = new String[input.totalTrusts][2];
for (int i = 0; i < input.totalTrusts; i++) {
input.trusts[i][0] = io.readLine();
input.trusts[i][1] = io.readLine();
}
final Output output = process.process(input);
io.write("%d\n", output.totalGroups);
}
io.close();
}
}
class Input {
public int totalPersons;
public int totalTrusts;
public String[] persons;
public String[][] trusts;
public boolean isEOF() {
return totalPersons == 0 && totalTrusts == 0;
}
}
class Output {
public int totalGroups;
}
class Process {
public Output process(final Input input) {
final Output output = new Output();
final List<String> vertices = Arrays.stream(input.persons).collect(Collectors.toList());
final List<List<String>> edges = Arrays.stream(input.trusts).map(Arrays::asList).collect(Collectors.toList());
final KosarajuAlgorithm<String> kosarajuAlgorithm = new KosarajuAlgorithm<>(vertices, edges);
final List<List<String>> stronglyConnectedComponents = kosarajuAlgorithm.getStronglyConnectedComponents();
output.totalGroups = stronglyConnectedComponents.size();
return output;
}
}
class KosarajuAlgorithm<V> {
private final DirectedGraph<V> graph;
private final DirectedGraph<V> transposeGraph;
private final List<List<V>> stronglyConnectedComponents;
public KosarajuAlgorithm(final List<V> vertices, final List<List<V>> edges) {
this.graph = createGraph(vertices, edges);
this.transposeGraph = createGraph(vertices, transpose(edges));
this.stronglyConnectedComponents = findStronglyConnectedComponents();
}
private List<List<V>> transpose(final List<List<V>> edges) {
return edges.stream()
.map(edge -> Arrays.asList(edge.get(1), edge.get(0)))
.collect(Collectors.toList());
}
private DirectedGraph<V> createGraph(final List<V> vertices, List<List<V>> edges) {
final DirectedGraph<V> graph = new DirectedGraph<>(vertices);
for (final List<V> edge : edges) {
graph.add(edge.get(0), edge.get(1));
}
return graph;
}
private List<List<V>> findStronglyConnectedComponents() {
final Set<V> visited = new HashSet<>();
final LinkedList<V> sequence = new LinkedList<>();
for (final V vertex : graph.get()) {
if (visited.contains(vertex)) continue;
dfs(graph, vertex, visited, sequence);
}
final List<List<V>> listSCC = new LinkedList<>();
visited.clear();
for (final Iterator<V> it = sequence.descendingIterator(); it.hasNext(); ) {
final V vertex = it.next();
if (visited.contains(vertex)) continue;
final LinkedList<V> scc = new LinkedList<>();
dfs(transposeGraph, vertex, visited, scc);
listSCC.add(new ArrayList<>(scc));
}
return new ArrayList<>(listSCC);
}
private void dfs(
final DirectedGraph<V> graph,
final V vertex,
final Set<V> visited,
final LinkedList<V> sequence
) {
if (visited.contains(vertex)) return;
visited.add(vertex);
graph.get(vertex).forEach(nextVertex -> dfs(graph, nextVertex, visited, sequence));
sequence.addLast(vertex);
}
public List<List<V>> getStronglyConnectedComponents() {
return stronglyConnectedComponents;
}
}
class DirectedGraph<V> {
private final Map<V, Set<V>> edges;
public DirectedGraph(final List<V> vertices) {
edges = new HashMap<>(vertices.size());
for (final V vertex : vertices) {
edges.putIfAbsent(vertex, new HashSet<>());
}
}
public void add(final V from, final V into) {
edges.computeIfAbsent(from, k -> new HashSet<>()).add(into);
}
public Set<V> get() {
return edges.keySet();
}
public Set<V> get(final V from) {
return edges.getOrDefault(from, Collections.emptySet());
}
}
final class BufferedIO {
private final BufferedReader in;
private final BufferedWriter out;
public BufferedIO(final InputStream in, final OutputStream out) {
this.in = new BufferedReader(new InputStreamReader(in));
this.out = new BufferedWriter(new OutputStreamWriter(out));
}
public String[] readLine(final String separator) throws IOException {
final String line = readLine();
return line == null ? null : line.split(separator);
}
public String readLine() throws IOException {
String line = in.readLine();
while (line != null && line.isEmpty()) line = in.readLine();
return line;
}
public void write(final String format, Object... args) throws IOException {
final String string = String.format(format, args);
write(string);
}
public void write(final String string) throws IOException {
out.write(string);
}
public void close() throws IOException {
in.close();
out.flush();
out.close();
}
}