Graph Representation — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Each task ships with a problem statement, constraints, hints, and a reference solution in all three languages. Validate against the evaluation criteria.
Beginner Tasks (5)¶
Task 1 — Build an adjacency list from an edge array¶
Problem. Given n vertices labelled 0..n-1 and a list of undirected edges, build the adjacency list and print each vertex's neighbors in insertion order.
Input / Output spec. - Input: n, m, then m lines u v. - Output: n lines, line i is i: followed by the space-separated neighbors of i.
Constraints. - 1 <= n <= 10^5, 0 <= m <= 2*10^5. - Vertices are in [0, n). Self-loops allowed; no parallel edges. - Build time: O(n + m).
Hint. Allocate n empty buckets first. For each edge append v to adj[u] and u to adj[v]. Never allocate an n x n matrix — for n = 10^5 that is 10^10 cells.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
adj := make([][]int, n)
for i := 0; i < m; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
adj[u] = append(adj[u], v)
adj[v] = append(adj[v], u)
}
for i := 0; i < n; i++ {
fmt.Fprintf(out, "%d:", i)
for _, v := range adj[i] {
fmt.Fprintf(out, " %d", v)
}
fmt.Fprintln(out)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task1 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer tok = new StreamTokenizer(br);
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int i = 0; i < m; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
adj.get(u).add(v);
adj.get(v).add(u);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(i).append(':');
for (int v : adj.get(i)) sb.append(' ').append(v);
sb.append('\n');
}
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
idx = 0
n = int(data[idx]); idx += 1
m = int(data[idx]); idx += 1
adj = [[] for _ in range(n)]
for _ in range(m):
u = int(data[idx]); v = int(data[idx + 1]); idx += 2
adj[u].append(v)
adj[v].append(u)
out = []
for i in range(n):
out.append(f"{i}:" + "".join(f" {v}" for v in adj[i]))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Build runs in O(n + m). - Each undirected edge appears in both endpoints' lists. - Isolated vertices print an empty neighbor list, not an error.
Task 2 — Build an adjacency matrix and answer edge queries¶
Problem. Given a small directed graph, build a V x V 0/1 adjacency matrix and answer a batch of hasEdge(u, v) queries in O(1) each.
Input / Output spec. - Input: n, m, then m lines u v (directed edge u -> v), then q, then q lines u v. - Output: q lines, true or false.
Constraints. - 1 <= n <= 1000 (so n^2 <= 10^6 cells). - 0 <= m, q <= 10^5. - Each query is O(1).
Hint. A matrix is the right call only because n <= 1000. For directed graphs set M[u][v] only — do not set the symmetric cell.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
mat := make([][]bool, n)
for i := range mat {
mat[i] = make([]bool, n)
}
for i := 0; i < m; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
mat[u][v] = true
}
var q int
fmt.Fscan(in, &q)
for i := 0; i < q; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
fmt.Fprintln(out, mat[u][v])
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task2 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
boolean[][] mat = new boolean[n][n];
for (int i = 0; i < m; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
mat[u][v] = true;
}
tok.nextToken(); int q = (int) tok.nval;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < q; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
sb.append(mat[u][v]).append('\n');
}
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
m = int(data[p]); p += 1
mat = [bytearray(n) for _ in range(n)]
for _ in range(m):
u = int(data[p]); v = int(data[p + 1]); p += 2
mat[u][v] = 1
q = int(data[p]); p += 1
out = []
for _ in range(q):
u = int(data[p]); v = int(data[p + 1]); p += 2
out.append("true" if mat[u][v] else "false")
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Each query is O(1). - Directed edges are not made symmetric. - A query hasEdge(u, u) returns true only if a self-loop was inserted.
Task 3 — Convert an edge list to an edge-count summary¶
Problem. Given an undirected edge list, report the degree of every vertex and the total edge count. This forces you to handle the handshake identity (sum of degrees = 2 * edges).
Input / Output spec. - Input: n, m, then m lines u v. - Output: line 1 is m (the edge count); line 2 is n space-separated degrees.
Constraints. - 1 <= n <= 10^6, 0 <= m <= 2*10^6. - O(n + m) time, O(n) extra memory. - Self-loops count 2 toward the degree of their vertex.
Hint. A single degree array. For each edge (u, v) increment deg[u] and deg[v]; a self-loop (v, v) naturally adds 2 to deg[v].
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
deg := make([]int, n)
for i := 0; i < m; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
deg[u]++
deg[v]++
}
fmt.Fprintln(out, m)
for i := 0; i < n; i++ {
if i > 0 {
out.WriteByte(' ')
}
fmt.Fprint(out, deg[i])
}
fmt.Fprintln(out)
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task3 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
int[] deg = new int[n];
for (int i = 0; i < m; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
deg[u]++;
deg[v]++;
}
StringBuilder sb = new StringBuilder();
sb.append(m).append('\n');
for (int i = 0; i < n; i++) {
if (i > 0) sb.append(' ');
sb.append(deg[i]);
}
System.out.println(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
m = int(data[p]); p += 1
deg = [0] * n
for _ in range(m):
u = int(data[p]); v = int(data[p + 1]); p += 2
deg[u] += 1
deg[v] += 1
sys.stdout.write(str(m) + "\n")
sys.stdout.write(" ".join(map(str, deg)) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Sum of degrees equals 2 * m (handshake identity). - Self-loops contribute 2 to the relevant degree. - Single pass, O(n) memory.
Task 4 — Detect a missing reverse edge (undirected validity check)¶
Problem. Given an adjacency list that claims to be undirected, verify it: for every edge u -> v there must be a matching v -> u. Report whether the list is a valid undirected graph.
Input / Output spec. - Input: n, then n lines; line i starts with k_i (the neighbor count) followed by k_i neighbor ids. - Output: true if every directed entry has its reverse, else false.
Constraints. - 1 <= n <= 10^5, total entries <= 4*10^5. - No parallel edges (each (u, v) appears at most once in adj[u]). - O(n + total entries) time.
Hint. Put every (u, v) with u < v into a set, and every (u, v) with u > v reversed to (v, u) into a count. An edge is valid iff both directions are present. Self-loops (v, v) must appear an even number of times overall, but with no parallel edges a self-loop appears once in adj[v] and is its own reverse.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
var n int
fmt.Fscan(in, &n)
type pair struct{ a, b int }
seen := map[pair]bool{}
adj := make([][]int, n)
for i := 0; i < n; i++ {
var k int
fmt.Fscan(in, &k)
adj[i] = make([]int, k)
for j := 0; j < k; j++ {
fmt.Fscan(in, &adj[i][j])
seen[pair{i, adj[i][j]}] = true
}
}
valid := true
for u := 0; u < n && valid; u++ {
for _, v := range adj[u] {
if !seen[pair{v, u}] {
valid = false
break
}
}
}
fmt.Println(valid)
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task4 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
Set<Long> seen = new HashSet<>();
int[][] adj = new int[n][];
for (int i = 0; i < n; i++) {
tok.nextToken(); int k = (int) tok.nval;
adj[i] = new int[k];
for (int j = 0; j < k; j++) {
tok.nextToken(); int v = (int) tok.nval;
adj[i][j] = v;
seen.add((long) i * 1_000_000_007L + v);
}
}
boolean valid = true;
outer:
for (int u = 0; u < n; u++) {
for (int v : adj[u]) {
if (!seen.contains((long) v * 1_000_000_007L + u)) {
valid = false;
break outer;
}
}
}
System.out.println(valid);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
adj = []
seen = set()
for i in range(n):
k = int(data[p]); p += 1
row = []
for _ in range(k):
v = int(data[p]); p += 1
row.append(v)
seen.add((i, v))
adj.append(row)
valid = all((v, u) in seen for u in range(n) for v in adj[u])
print(str(valid).lower())
if __name__ == "__main__":
main()
Evaluation criteria. - Correctly flags a one-directional edge as invalid. - Self-loops are treated as their own reverse. - Runs in O(n + total entries).
Task 5 — Build a weighted adjacency list¶
Problem. Given an undirected weighted graph, build an adjacency list of (neighbor, weight) pairs and answer weight(u, v) queries, returning -1 if no such edge exists.
Input / Output spec. - Input: n, m, then m lines u v w, then q, then q lines u v. - Output: q lines, each the weight of edge (u, v) or -1.
Constraints. - 1 <= n <= 10^5, 0 <= m, q <= 2*10^5. - 1 <= w <= 10^9. No parallel edges. - Each query is O(degree(u)).
Hint. Store (neighbor, weight) pairs. For a query scan adj[u] for v. Note that 0 is not used as the sentinel here because weights are positive; we return -1 for "no edge".
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
type wEdge struct {
to int
w int64
}
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
adj := make([][]wEdge, n)
for i := 0; i < m; i++ {
var u, v int
var w int64
fmt.Fscan(in, &u, &v, &w)
adj[u] = append(adj[u], wEdge{v, w})
adj[v] = append(adj[v], wEdge{u, w})
}
var q int
fmt.Fscan(in, &q)
for i := 0; i < q; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
var ans int64 = -1
for _, e := range adj[u] {
if e.to == v {
ans = e.w
break
}
}
fmt.Fprintln(out, ans)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task5 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
List<long[]>[] adj = new List[n];
for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
for (int i = 0; i < m; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
tok.nextToken(); long w = (long) tok.nval;
adj[u].add(new long[]{v, w});
adj[v].add(new long[]{u, w});
}
tok.nextToken(); int q = (int) tok.nval;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < q; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
long ans = -1;
for (long[] e : adj[u]) {
if (e[0] == v) { ans = e[1]; break; }
}
sb.append(ans).append('\n');
}
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
m = int(data[p]); p += 1
adj = [[] for _ in range(n)]
for _ in range(m):
u = int(data[p]); v = int(data[p + 1]); w = int(data[p + 2]); p += 3
adj[u].append((v, w))
adj[v].append((u, w))
q = int(data[p]); p += 1
out = []
for _ in range(q):
u = int(data[p]); v = int(data[p + 1]); p += 2
ans = -1
for to, w in adj[u]:
if to == v:
ans = w
break
out.append(str(ans))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Returns -1 for a non-existent edge, never 0. - Stores each undirected edge in both endpoints' lists with the same weight. - Each query is O(degree(u)).
Intermediate Tasks (5)¶
Task 6 — Build CSR from an edge list and answer neighbor queries¶
Problem. Build a Compressed Sparse Row (CSR) representation of a directed graph using the two-pass counting algorithm, then answer neighbors(u) queries by returning the slice target[offset[u]:offset[u+1]].
Input / Output spec. - Input: n, m, then m lines u v (directed), then q, then q query vertices. - Output: q lines; each line is the space-separated out-neighbors of the queried vertex (in edge-input order), or empty.
Constraints. - 1 <= n <= 10^6, 0 <= m <= 5*10^6, 0 <= q <= 10^5. - Build: O(n + m). Each query: O(degree(u)).
Hint. Three steps: count out-degrees into offset[u+1]; prefix-sum offset; then place each target using a separate cursor copy so you never corrupt offset.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
us := make([]int, m)
vs := make([]int, m)
offset := make([]int, n+1)
for i := 0; i < m; i++ {
fmt.Fscan(in, &us[i], &vs[i])
offset[us[i]+1]++
}
for i := 1; i <= n; i++ {
offset[i] += offset[i-1]
}
target := make([]int, m)
cursor := make([]int, n)
copy(cursor, offset[:n])
for i := 0; i < m; i++ {
u := us[i]
target[cursor[u]] = vs[i]
cursor[u]++
}
var q int
fmt.Fscan(in, &q)
for i := 0; i < q; i++ {
var u int
fmt.Fscan(in, &u)
first := true
for k := offset[u]; k < offset[u+1]; k++ {
if !first {
out.WriteByte(' ')
}
fmt.Fprint(out, target[k])
first = false
}
fmt.Fprintln(out)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task6 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
int[] us = new int[m], vs = new int[m];
int[] offset = new int[n + 1];
for (int i = 0; i < m; i++) {
tok.nextToken(); us[i] = (int) tok.nval;
tok.nextToken(); vs[i] = (int) tok.nval;
offset[us[i] + 1]++;
}
for (int i = 1; i <= n; i++) offset[i] += offset[i - 1];
int[] target = new int[m];
int[] cursor = Arrays.copyOf(offset, n);
for (int i = 0; i < m; i++) target[cursor[us[i]]++] = vs[i];
tok.nextToken(); int q = (int) tok.nval;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < q; i++) {
tok.nextToken(); int u = (int) tok.nval;
for (int k = offset[u]; k < offset[u + 1]; k++) {
if (k > offset[u]) sb.append(' ');
sb.append(target[k]);
}
sb.append('\n');
}
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
m = int(data[p]); p += 1
us = [0] * m
vs = [0] * m
offset = [0] * (n + 1)
for i in range(m):
u = int(data[p]); v = int(data[p + 1]); p += 2
us[i] = u
vs[i] = v
offset[u + 1] += 1
for i in range(1, n + 1):
offset[i] += offset[i - 1]
target = [0] * m
cursor = offset[:n]
for i in range(m):
u = us[i]
target[cursor[u]] = vs[i]
cursor[u] += 1
q = int(data[p]); p += 1
out = []
for _ in range(q):
u = int(data[p]); p += 1
out.append(" ".join(map(str, target[offset[u]:offset[u + 1]])))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - offset[u+1] - offset[u] equals the out-degree of u. - cursor is a separate copy; offset is never mutated during placement. - Build is O(n + m); each query is O(degree).
Task 7 — Convert an adjacency list to a deduplicated edge list¶
Problem. Given an undirected adjacency list, output the edge list with each undirected edge listed exactly once. Filter using the u < v convention so (a, b) and (b, a) collapse to one entry. Sort the output for determinism.
Input / Output spec. - Input: n, then n lines; line i is k_i followed by k_i neighbors. - Output: line 1 is the edge count e; then e lines u v with u <= v, sorted ascending.
Constraints. - 1 <= n <= 10^5, total entries <= 4*10^5. - Self-loops (v, v) are emitted once. - O(n + total entries + e log e) time.
Hint. For every stored (u, v), emit it only if u <= v. This relies on the list being a valid undirected graph (each edge stored at both endpoints), so the u < v half is exactly one copy; self-loops have u == v.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
"sort"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n int
fmt.Fscan(in, &n)
type edge struct{ u, v int }
var edges []edge
for u := 0; u < n; u++ {
var k int
fmt.Fscan(in, &k)
for j := 0; j < k; j++ {
var v int
fmt.Fscan(in, &v)
if u <= v {
edges = append(edges, edge{u, v})
}
}
}
sort.Slice(edges, func(i, j int) bool {
if edges[i].u != edges[j].u {
return edges[i].u < edges[j].u
}
return edges[i].v < edges[j].v
})
fmt.Fprintln(out, len(edges))
for _, e := range edges {
fmt.Fprintln(out, e.u, e.v)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task7 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
List<int[]> edges = new ArrayList<>();
for (int u = 0; u < n; u++) {
tok.nextToken(); int k = (int) tok.nval;
for (int j = 0; j < k; j++) {
tok.nextToken(); int v = (int) tok.nval;
if (u <= v) edges.add(new int[]{u, v});
}
}
edges.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
StringBuilder sb = new StringBuilder();
sb.append(edges.size()).append('\n');
for (int[] e : edges) sb.append(e[0]).append(' ').append(e[1]).append('\n');
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
edges = []
for u in range(n):
k = int(data[p]); p += 1
for _ in range(k):
v = int(data[p]); p += 1
if u <= v:
edges.append((u, v))
edges.sort()
out = [str(len(edges))]
out.extend(f"{u} {v}" for u, v in edges)
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Each undirected edge appears exactly once in the output. - Self-loops appear exactly once. - Output is sorted; edge count matches len(edges).
Task 8 — Implicit grid graph: count connected components¶
Problem. Given a grid of . (open) and # (wall), count the connected components of open cells, treating each open cell as a vertex connected to its orthogonal open neighbors. Do not build an explicit graph — compute neighbors on the fly.
Input / Output spec. - Input: rows, cols, then rows lines of the grid. - Output: the number of connected components of open cells.
Constraints. - 1 <= rows, cols <= 1000. - 4-directional connectivity. - O(rows * cols) time and memory.
Hint. The grid is the graph. Run BFS/DFS from each unvisited open cell, using the four offset deltas and bounds checks; each BFS marks one component.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
var rows, cols int
fmt.Fscan(in, &rows, &cols)
grid := make([]string, rows)
for i := range grid {
fmt.Fscan(in, &grid[i])
}
seen := make([][]bool, rows)
for i := range seen {
seen[i] = make([]bool, cols)
}
deltas := [4][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
count := 0
queue := make([][2]int, 0, rows*cols)
for r := 0; r < rows; r++ {
for c := 0; c < cols; c++ {
if grid[r][c] != '.' || seen[r][c] {
continue
}
count++
queue = queue[:0]
queue = append(queue, [2]int{r, c})
seen[r][c] = true
for len(queue) > 0 {
cell := queue[0]
queue = queue[1:]
for _, d := range deltas {
nr, nc := cell[0]+d[0], cell[1]+d[1]
if nr >= 0 && nr < rows && nc >= 0 && nc < cols &&
grid[nr][nc] == '.' && !seen[nr][nc] {
seen[nr][nc] = true
queue = append(queue, [2]int{nr, nc})
}
}
}
}
}
fmt.Println(count)
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task8 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int rows = Integer.parseInt(st.nextToken());
int cols = Integer.parseInt(st.nextToken());
char[][] grid = new char[rows][];
for (int i = 0; i < rows; i++) grid[i] = br.readLine().toCharArray();
boolean[][] seen = new boolean[rows][cols];
int[][] deltas = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int count = 0;
ArrayDeque<int[]> queue = new ArrayDeque<>();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (grid[r][c] != '.' || seen[r][c]) continue;
count++;
queue.clear();
queue.add(new int[]{r, c});
seen[r][c] = true;
while (!queue.isEmpty()) {
int[] cell = queue.poll();
for (int[] d : deltas) {
int nr = cell[0] + d[0], nc = cell[1] + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& grid[nr][nc] == '.' && !seen[nr][nc]) {
seen[nr][nc] = true;
queue.add(new int[]{nr, nc});
}
}
}
}
}
System.out.println(count);
}
}
Reference — Python.
import sys
from collections import deque
def main() -> None:
data = sys.stdin.buffer.read().split()
rows = int(data[0])
cols = int(data[1])
grid = [data[2 + i].decode() for i in range(rows)]
seen = [[False] * cols for _ in range(rows)]
deltas = ((-1, 0), (1, 0), (0, -1), (0, 1))
count = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] != "." or seen[r][c]:
continue
count += 1
q = deque([(r, c)])
seen[r][c] = True
while q:
cr, cc = q.popleft()
for dr, dc in deltas:
nr, nc = cr + dr, cc + dc
if 0 <= nr < rows and 0 <= nc < cols and \
grid[nr][nc] == "." and not seen[nr][nc]:
seen[nr][nc] = True
q.append((nr, nc))
print(count)
if __name__ == "__main__":
main()
Evaluation criteria. - No explicit adjacency structure is built; neighbors are computed with deltas. - A grid of all walls yields 0; a fully open grid yields 1. - O(rows * cols) time and memory.
Task 9 — Reverse (transpose) a directed graph¶
Problem. Given a directed graph as an adjacency list, build its transpose: the graph with every edge reversed (u -> v becomes v -> u). Output the transpose as an adjacency list.
Input / Output spec. - Input: n, m, then m lines u v (directed). - Output: n lines; line i is i: followed by the sorted out-neighbors of i in the transpose.
Constraints. - 1 <= n <= 10^5, 0 <= m <= 5*10^5. - O(n + m + m log m) time (the sort is for deterministic output).
Hint. Allocate n buckets for the transpose. For each edge (u, v), append u to radj[v]. Sorting each bucket makes the output deterministic; the transpose itself needs no sort.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
"sort"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
radj := make([][]int, n)
for i := 0; i < m; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
radj[v] = append(radj[v], u)
}
for i := 0; i < n; i++ {
sort.Ints(radj[i])
fmt.Fprintf(out, "%d:", i)
for _, v := range radj[i] {
fmt.Fprintf(out, " %d", v)
}
fmt.Fprintln(out)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task9 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
List<List<Integer>> radj = new ArrayList<>();
for (int i = 0; i < n; i++) radj.add(new ArrayList<>());
for (int i = 0; i < m; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
radj.get(v).add(u);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
Collections.sort(radj.get(i));
sb.append(i).append(':');
for (int v : radj.get(i)) sb.append(' ').append(v);
sb.append('\n');
}
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
m = int(data[p]); p += 1
radj = [[] for _ in range(n)]
for _ in range(m):
u = int(data[p]); v = int(data[p + 1]); p += 2
radj[v].append(u)
out = []
for i in range(n):
radj[i].sort()
out.append(f"{i}:" + "".join(f" {v}" for v in radj[i]))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Edge u -> v becomes v -> u in the output. - The transpose of the transpose equals the original graph. - Runs in O(n + m) plus the per-bucket sort.
Task 10 — Relabel a string-vertex graph to integer ids¶
Problem. Vertices arrive as arbitrary strings. Assign each a stable integer id in first-appearance order, build the (directed) adjacency list over ids, and answer neighbors(label) queries returning the neighbor labels.
Input / Output spec. - Input: m, then m lines srcLabel dstLabel, then q, then q query labels. - Output: q lines; each is the space-separated neighbor labels of the queried vertex (in edge-input order), or empty if the label is unknown or has no out-edges.
Constraints. - 0 <= m, q <= 10^5. Labels are non-empty alphanumeric strings. - id -> label and label -> id maps at the boundary; core stored over ints.
Hint. Keep labelToId (map) and idToLabel (array). On each new label, assign the next id and append to idToLabel. Work with ids internally; translate back only at output.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
labelToId := map[string]int{}
var idToLabel []string
intern := func(s string) int {
if id, ok := labelToId[s]; ok {
return id
}
id := len(idToLabel)
labelToId[s] = id
idToLabel = append(idToLabel, s)
return id
}
var m int
fmt.Fscan(in, &m)
adj := map[int][]int{}
for i := 0; i < m; i++ {
var s, d string
fmt.Fscan(in, &s, &d)
su, dv := intern(s), intern(d)
adj[su] = append(adj[su], dv)
}
var q int
fmt.Fscan(in, &q)
for i := 0; i < q; i++ {
var s string
fmt.Fscan(in, &s)
id, ok := labelToId[s]
if !ok {
fmt.Fprintln(out)
continue
}
first := true
for _, nb := range adj[id] {
if !first {
out.WriteByte(' ')
}
out.WriteString(idToLabel[nb])
first = false
}
fmt.Fprintln(out)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task10 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Map<String, Integer> labelToId = new HashMap<>();
List<String> idToLabel = new ArrayList<>();
Map<Integer, List<Integer>> adj = new HashMap<>();
int m = Integer.parseInt(br.readLine().trim());
for (int i = 0; i < m; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
int su = intern(st.nextToken(), labelToId, idToLabel);
int dv = intern(st.nextToken(), labelToId, idToLabel);
adj.computeIfAbsent(su, k -> new ArrayList<>()).add(dv);
}
int q = Integer.parseInt(br.readLine().trim());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < q; i++) {
String s = br.readLine().trim();
Integer id = labelToId.get(s);
if (id != null && adj.containsKey(id)) {
List<Integer> ns = adj.get(id);
for (int j = 0; j < ns.size(); j++) {
if (j > 0) sb.append(' ');
sb.append(idToLabel.get(ns.get(j)));
}
}
sb.append('\n');
}
System.out.print(sb);
}
static int intern(String s, Map<String, Integer> map, List<String> rev) {
Integer id = map.get(s);
if (id != null) return id;
int newId = rev.size();
map.put(s, newId);
rev.add(s);
return newId;
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
label_to_id = {}
id_to_label = []
def intern(s: str) -> int:
if s in label_to_id:
return label_to_id[s]
new_id = len(id_to_label)
label_to_id[s] = new_id
id_to_label.append(s)
return new_id
m = int(data[p]); p += 1
adj = {}
for _ in range(m):
s = data[p].decode(); d = data[p + 1].decode(); p += 2
su, dv = intern(s), intern(d)
adj.setdefault(su, []).append(dv)
q = int(data[p]); p += 1
out = []
for _ in range(q):
s = data[p].decode(); p += 1
idv = label_to_id.get(s)
if idv is None or idv not in adj:
out.append("")
else:
out.append(" ".join(id_to_label[nb] for nb in adj[idv]))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Ids are assigned in first-appearance order. - Unknown query labels produce an empty line, not a crash. - Internal storage uses integer ids; labels appear only at the boundary.
Advanced Tasks (5)¶
Task 11 — Weighted CSR with a parallel weight array¶
Problem. Build a weighted CSR for a directed graph: offset[], target[], and a parallel weight[] aligned with target[] so weight[k] is the weight of the edge ending at target[k]. Answer neighbors(u) queries returning (target, weight) pairs.
Input / Output spec. - Input: n, m, then m lines u v w, then q, then q query vertices. - Output: q lines; each is space-separated v:w pairs for the queried vertex's out-edges (in input order).
Constraints. - 1 <= n <= 10^6, 0 <= m <= 5*10^6. - 1 <= w <= 10^9. - Build: O(n + m). Each query: O(degree).
Hint. Same two-pass build as plain CSR, but place weight[cursor[u]] in lockstep with target[cursor[u]]. An off-by-one between the two arrays is a silent correctness bug.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
us := make([]int, m)
vs := make([]int, m)
ws := make([]int64, m)
offset := make([]int, n+1)
for i := 0; i < m; i++ {
fmt.Fscan(in, &us[i], &vs[i], &ws[i])
offset[us[i]+1]++
}
for i := 1; i <= n; i++ {
offset[i] += offset[i-1]
}
target := make([]int, m)
weight := make([]int64, m)
cursor := make([]int, n)
copy(cursor, offset[:n])
for i := 0; i < m; i++ {
u := us[i]
k := cursor[u]
target[k] = vs[i]
weight[k] = ws[i]
cursor[u]++
}
var q int
fmt.Fscan(in, &q)
for i := 0; i < q; i++ {
var u int
fmt.Fscan(in, &u)
for k := offset[u]; k < offset[u+1]; k++ {
if k > offset[u] {
out.WriteByte(' ')
}
fmt.Fprintf(out, "%d:%d", target[k], weight[k])
}
fmt.Fprintln(out)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task11 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
int[] us = new int[m], vs = new int[m];
long[] ws = new long[m];
int[] offset = new int[n + 1];
for (int i = 0; i < m; i++) {
tok.nextToken(); us[i] = (int) tok.nval;
tok.nextToken(); vs[i] = (int) tok.nval;
tok.nextToken(); ws[i] = (long) tok.nval;
offset[us[i] + 1]++;
}
for (int i = 1; i <= n; i++) offset[i] += offset[i - 1];
int[] target = new int[m];
long[] weight = new long[m];
int[] cursor = Arrays.copyOf(offset, n);
for (int i = 0; i < m; i++) {
int k = cursor[us[i]]++;
target[k] = vs[i];
weight[k] = ws[i];
}
tok.nextToken(); int q = (int) tok.nval;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < q; i++) {
tok.nextToken(); int u = (int) tok.nval;
for (int k = offset[u]; k < offset[u + 1]; k++) {
if (k > offset[u]) sb.append(' ');
sb.append(target[k]).append(':').append(weight[k]);
}
sb.append('\n');
}
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
m = int(data[p]); p += 1
us = [0] * m
vs = [0] * m
ws = [0] * m
offset = [0] * (n + 1)
for i in range(m):
u = int(data[p]); v = int(data[p + 1]); w = int(data[p + 2]); p += 3
us[i], vs[i], ws[i] = u, v, w
offset[u + 1] += 1
for i in range(1, n + 1):
offset[i] += offset[i - 1]
target = [0] * m
weight = [0] * m
cursor = offset[:n]
for i in range(m):
u = us[i]
k = cursor[u]
target[k] = vs[i]
weight[k] = ws[i]
cursor[u] += 1
q = int(data[p]); p += 1
out = []
for _ in range(q):
u = int(data[p]); p += 1
pairs = [f"{target[k]}:{weight[k]}"
for k in range(offset[u], offset[u + 1])]
out.append(" ".join(pairs))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - weight[k] always corresponds to the edge ending at target[k]. - Build is O(n + m); queries O(degree). - A randomized cross-check against a (neighbor, weight) adjacency list matches exactly.
Task 12 — Snapshot graph store with atomic swap (concurrent reads)¶
Problem. Implement a read-mostly graph store: an immutable CSR snapshot serves lock-free neighbors(u) reads; addEdge buffers into a side log; rebuild folds the buffer into a fresh CSR and publishes it atomically. Demonstrate that reads taken before a rebuild see the old graph and reads after see the new one.
Input / Output spec. - Operations: add u v, rebuild, query u. - Output: one line per query — the space-separated current neighbors of u.
Constraints. - 1 <= n <= 10^5. - Reads must not lock; writes are applied only on rebuild. - Each rebuild is O(n + E).
Hint. Hold the current CSR behind an atomic pointer (atomic.Pointer / AtomicReference). query reads through it without locking; add appends under a small mutex; rebuild sorts all edges by source and swaps a new immutable CSR in.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
"sort"
"sync"
"sync/atomic"
)
type CSR struct {
offset []int
target []int
}
func (c *CSR) Neighbors(u int) []int { return c.target[c.offset[u]:c.offset[u+1]] }
func buildCSR(n int, edges [][2]int) *CSR {
offset := make([]int, n+1)
for _, e := range edges {
offset[e[0]+1]++
}
for i := 1; i <= n; i++ {
offset[i] += offset[i-1]
}
target := make([]int, len(edges))
cursor := make([]int, n)
copy(cursor, offset[:n])
for _, e := range edges {
target[cursor[e[0]]] = e[1]
cursor[e[0]]++
}
return &CSR{offset, target}
}
type Store struct {
n int
current atomic.Pointer[CSR]
mu sync.Mutex
base [][2]int
}
func NewStore(n int) *Store {
s := &Store{n: n}
s.current.Store(buildCSR(n, nil))
return s
}
func (s *Store) AddEdge(u, v int) {
s.mu.Lock()
s.base = append(s.base, [2]int{u, v})
s.mu.Unlock()
}
func (s *Store) Rebuild() {
s.mu.Lock()
all := append([][2]int(nil), s.base...)
s.mu.Unlock()
sort.Slice(all, func(i, j int) bool { return all[i][0] < all[j][0] })
s.current.Store(buildCSR(s.n, all))
}
func (s *Store) Neighbors(u int) []int { return s.current.Load().Neighbors(u) }
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n int
fmt.Fscan(in, &n)
store := NewStore(n)
var op string
for {
if _, err := fmt.Fscan(in, &op); err != nil {
return
}
switch op {
case "add":
var u, v int
fmt.Fscan(in, &u, &v)
store.AddEdge(u, v)
case "rebuild":
store.Rebuild()
case "query":
var u int
fmt.Fscan(in, &u)
ns := store.Neighbors(u)
for i, v := range ns {
if i > 0 {
out.WriteByte(' ')
}
fmt.Fprint(out, v)
}
fmt.Fprintln(out)
}
}
}
Reference — Java.
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.io.*;
public class Task12 {
static final class CSR {
final int[] offset, target;
CSR(int[] o, int[] t) { offset = o; target = t; }
int[] neighbors(int u) { return Arrays.copyOfRange(target, offset[u], offset[u + 1]); }
}
static CSR build(int n, List<int[]> edges) {
int[] offset = new int[n + 1];
for (int[] e : edges) offset[e[0] + 1]++;
for (int i = 1; i <= n; i++) offset[i] += offset[i - 1];
int[] target = new int[edges.size()];
int[] cursor = Arrays.copyOf(offset, n);
for (int[] e : edges) target[cursor[e[0]]++] = e[1];
return new CSR(offset, target);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine().trim());
AtomicReference<CSR> current = new AtomicReference<>(build(n, new ArrayList<>()));
List<int[]> base = new ArrayList<>();
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
if (line.isBlank()) continue;
StringTokenizer st = new StringTokenizer(line);
String op = st.nextToken();
switch (op) {
case "add" -> base.add(new int[]{
Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken())});
case "rebuild" -> {
List<int[]> all = new ArrayList<>(base);
all.sort((a, b) -> a[0] - b[0]);
current.set(build(n, all));
}
case "query" -> {
int u = Integer.parseInt(st.nextToken());
int[] ns = current.get().neighbors(u);
for (int i = 0; i < ns.length; i++) {
if (i > 0) sb.append(' ');
sb.append(ns[i]);
}
sb.append('\n');
}
}
}
System.out.print(sb);
}
}
Reference — Python.
import sys
class CSR:
__slots__ = ("offset", "target")
def __init__(self, offset, target):
self.offset = offset
self.target = target
def neighbors(self, u):
return self.target[self.offset[u]:self.offset[u + 1]]
def build_csr(n, edges):
offset = [0] * (n + 1)
for u, _ in edges:
offset[u + 1] += 1
for i in range(1, n + 1):
offset[i] += offset[i - 1]
target = [0] * len(edges)
cursor = offset[:n]
for u, v in edges:
target[cursor[u]] = v
cursor[u] += 1
return CSR(offset, target)
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
base = []
current = build_csr(n, base) # GIL makes the pointer swap atomic in CPython
out = []
while p < len(data):
op = data[p].decode(); p += 1
if op == "add":
u = int(data[p]); v = int(data[p + 1]); p += 2
base.append((u, v))
elif op == "rebuild":
current = build_csr(n, sorted(base))
elif op == "query":
u = int(data[p]); p += 1
out.append(" ".join(map(str, current.neighbors(u))))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - query between rebuilds reflects the last published snapshot, not buffered writes. - Reads dereference one atomic pointer with no lock. - Each rebuild is O(n + E) and produces a valid CSR.
Task 13 — Gap + varint compressed adjacency storage¶
Problem. Compress an undirected graph's adjacency lists with gap encoding plus byte-aligned varints: sort each vertex's neighbors, store first-differences, and encode each gap as a LEB128 varint. Answer neighbors(u) by decoding the byte stream. Report the total compressed byte size.
Input / Output spec. - Input: n, m, then m lines u v, then q, then q query vertices. - Output: line 1 is the total compressed byte size; then q lines, each the decoded ascending neighbors of the queried vertex.
Constraints. - 1 <= n <= 10^5, 0 <= m <= 5*10^5. - Neighbor ids fit in 32 bits. - Decode is O(degree) per query.
Hint. Per vertex: sort neighbors, store first then gap = cur - prev for the rest. LEB128 writes 7 bits per byte, high bit set while more bytes follow. Keep a per-vertex byte offset index so you can seek to a vertex's stream.
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
"sort"
)
func putVarint(buf []byte, x uint32) []byte {
for x >= 0x80 {
buf = append(buf, byte(x)|0x80)
x >>= 7
}
return append(buf, byte(x))
}
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n, m int
fmt.Fscan(in, &n, &m)
adj := make([][]int, n)
for i := 0; i < m; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
adj[u] = append(adj[u], v)
adj[v] = append(adj[v], u)
}
starts := make([]int, n+1)
var stream []byte
for u := 0; u < n; u++ {
starts[u] = len(stream)
ns := adj[u]
sort.Ints(ns)
prev := 0
for j, v := range ns {
gap := v - prev
if j == 0 {
gap = v
}
stream = putVarint(stream, uint32(gap))
prev = v
}
}
starts[n] = len(stream)
degree := make([]int, n)
for u := 0; u < n; u++ {
degree[u] = len(adj[u])
}
fmt.Fprintln(out, len(stream))
decode := func(u int) []int {
res := make([]int, 0, degree[u])
pos := starts[u]
prev := 0
for len(res) < degree[u] {
var x uint32
var shift uint
for {
b := stream[pos]
pos++
x |= uint32(b&0x7f) << shift
if b < 0x80 {
break
}
shift += 7
}
var v int
if len(res) == 0 {
v = int(x)
} else {
v = prev + int(x)
}
res = append(res, v)
prev = v
}
return res
}
var q int
fmt.Fscan(in, &q)
for i := 0; i < q; i++ {
var u int
fmt.Fscan(in, &u)
ns := decode(u)
for j, v := range ns {
if j > 0 {
out.WriteByte(' ')
}
fmt.Fprint(out, v)
}
fmt.Fprintln(out)
}
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task13 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int i = 0; i < m; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
adj.get(u).add(v);
adj.get(v).add(u);
}
int[] starts = new int[n + 1];
int[] degree = new int[n];
ByteArrayOutputStream stream = new ByteArrayOutputStream();
for (int u = 0; u < n; u++) {
starts[u] = stream.size();
List<Integer> ns = adj.get(u);
Collections.sort(ns);
degree[u] = ns.size();
int prev = 0;
for (int j = 0; j < ns.size(); j++) {
int v = ns.get(j);
int gap = (j == 0) ? v : v - prev;
putVarint(stream, gap);
prev = v;
}
}
starts[n] = stream.size();
byte[] bytes = stream.toByteArray();
tok.nextToken(); int q = (int) tok.nval;
StringBuilder sb = new StringBuilder();
sb.append(bytes.length).append('\n');
for (int i = 0; i < q; i++) {
tok.nextToken(); int u = (int) tok.nval;
int pos = starts[u], prev = 0;
for (int c = 0; c < degree[u]; c++) {
int x = 0, shift = 0;
while (true) {
int b = bytes[pos++] & 0xff;
x |= (b & 0x7f) << shift;
if (b < 0x80) break;
shift += 7;
}
int v = (c == 0) ? x : prev + x;
if (c > 0) sb.append(' ');
sb.append(v);
prev = v;
}
sb.append('\n');
}
System.out.print(sb);
}
static void putVarint(ByteArrayOutputStream s, int x) {
while ((x & ~0x7f) != 0) {
s.write((x & 0x7f) | 0x80);
x >>>= 7;
}
s.write(x);
}
}
Reference — Python.
import sys
def put_varint(buf: bytearray, x: int) -> None:
while x >= 0x80:
buf.append((x & 0x7f) | 0x80)
x >>= 7
buf.append(x)
def main() -> None:
data = sys.stdin.buffer.read().split()
p = 0
n = int(data[p]); p += 1
m = int(data[p]); p += 1
adj = [[] for _ in range(n)]
for _ in range(m):
u = int(data[p]); v = int(data[p + 1]); p += 2
adj[u].append(v)
adj[v].append(u)
starts = [0] * (n + 1)
degree = [0] * n
stream = bytearray()
for u in range(n):
starts[u] = len(stream)
ns = sorted(adj[u])
degree[u] = len(ns)
prev = 0
for j, v in enumerate(ns):
gap = v if j == 0 else v - prev
put_varint(stream, gap)
prev = v
starts[n] = len(stream)
def decode(u: int):
res = []
pos = starts[u]
prev = 0
while len(res) < degree[u]:
x = 0
shift = 0
while True:
b = stream[pos]
pos += 1
x |= (b & 0x7f) << shift
if b < 0x80:
break
shift += 7
v = x if not res else prev + x
res.append(v)
prev = v
return res
q = int(data[p]); p += 1
out = [str(len(stream))]
for _ in range(q):
u = int(data[p]); p += 1
out.append(" ".join(map(str, decode(u))))
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - Decoded neighbors equal the sorted original adjacency list. - Compressed size is smaller than 4 * (2m) bytes on locality-rich inputs. - Decode is O(degree) per query.
Task 14 — Partition a graph and measure the cross-edge ratio¶
Problem. Given a graph and a number of partitions P, assign each vertex to partition hash(v) mod P (use v mod P for determinism). Compute the fraction of edges whose endpoints fall in different partitions — the cross-edge ratio that drives network cost in distributed graphs.
Input / Output spec. - Input: n, m, P, then m lines u v (undirected). - Output: the cross-edge ratio as a fraction rounded to 6 decimals.
Constraints. - 1 <= n <= 10^6, 0 <= m <= 5*10^6, 1 <= P <= n. - O(n + m) time.
Hint. part(v) = v % P. An edge is a cross-edge iff part(u) != part(v). Ratio is crossEdges / m (define ratio as 0 when m == 0).
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
var n, m, p int
fmt.Fscan(in, &n, &m, &p)
cross := 0
for i := 0; i < m; i++ {
var u, v int
fmt.Fscan(in, &u, &v)
if u%p != v%p {
cross++
}
}
ratio := 0.0
if m > 0 {
ratio = float64(cross) / float64(m)
}
fmt.Printf("%.6f\n", ratio)
_ = n
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task14 {
public static void main(String[] args) throws IOException {
StreamTokenizer tok = new StreamTokenizer(
new BufferedReader(new InputStreamReader(System.in)));
tok.nextToken(); int n = (int) tok.nval;
tok.nextToken(); int m = (int) tok.nval;
tok.nextToken(); int p = (int) tok.nval;
long cross = 0;
for (int i = 0; i < m; i++) {
tok.nextToken(); int u = (int) tok.nval;
tok.nextToken(); int v = (int) tok.nval;
if (u % p != v % p) cross++;
}
double ratio = (m > 0) ? (double) cross / m : 0.0;
System.out.printf("%.6f%n", ratio);
}
}
Reference — Python.
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
idx = 0
n = int(data[idx]); idx += 1
m = int(data[idx]); idx += 1
p = int(data[idx]); idx += 1
cross = 0
for _ in range(m):
u = int(data[idx]); v = int(data[idx + 1]); idx += 2
if u % p != v % p:
cross += 1
ratio = cross / m if m > 0 else 0.0
print(f"{ratio:.6f}")
if __name__ == "__main__":
main()
Evaluation criteria. - P = 1 yields ratio 0.0 (all vertices in one partition). - Empty graph (m = 0) yields 0.0, not a division error. - Single pass, O(n + m).
Task 15 — Choose the representation and measure its memory footprint¶
Problem. Given graph parameters V and E and a flag dense or sparse, compute the estimated bytes for each of matrix, primitive adjacency list, edge list, and CSR (using 4-byte ids and the model from the senior notes), then print which representation a senior engineer would pick and why (one of matrix, list, edge, csr).
Input / Output spec. - Input: V, E, then a string static or mutable. - Output: 4 lines matrix=<bytes>, list=<bytes>, edge=<bytes>, csr=<bytes>, then a 5th line choice=<name>.
Constraints. - 1 <= V <= 2*10^8, 0 <= E <= 4*10^10. - Use 64-bit arithmetic for byte counts. - Model (directed, 4-byte ids): matrix = 4*V*V; list = 4*E + 40*V (header + slack); edge = 8*E; csr = 4*(V+1) + 4*E.
Hint. Decision rule: if E >= V*V/4 (dense and V small enough that the matrix fits a sane budget, say V <= 5000) choose matrix; else if static choose csr; else choose list. The edge form is reported but only chosen when explicitly asked for whole-edge sweeps (not in this task's rule).
Reference — Go.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
var v, e int64
var kind string
fmt.Fscan(in, &v, &e, &kind)
matrix := 4 * v * v
list := 4*e + 40*v
edge := 8 * e
csr := 4*(v+1) + 4*e
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
fmt.Fprintf(out, "matrix=%d\n", matrix)
fmt.Fprintf(out, "list=%d\n", list)
fmt.Fprintf(out, "edge=%d\n", edge)
fmt.Fprintf(out, "csr=%d\n", csr)
choice := "list"
if v <= 5000 && e >= v*v/4 {
choice = "matrix"
} else if kind == "static" {
choice = "csr"
}
fmt.Fprintf(out, "choice=%s\n", choice)
}
Reference — Java.
import java.util.*;
import java.io.*;
public class Task15 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
long v = Long.parseLong(st.nextToken());
long e = Long.parseLong(st.nextToken());
String kind = st.nextToken();
long matrix = 4L * v * v;
long list = 4L * e + 40L * v;
long edge = 8L * e;
long csr = 4L * (v + 1) + 4L * e;
StringBuilder sb = new StringBuilder();
sb.append("matrix=").append(matrix).append('\n');
sb.append("list=").append(list).append('\n');
sb.append("edge=").append(edge).append('\n');
sb.append("csr=").append(csr).append('\n');
String choice = "list";
if (v <= 5000 && e >= v * v / 4) choice = "matrix";
else if (kind.equals("static")) choice = "csr";
sb.append("choice=").append(choice).append('\n');
System.out.print(sb);
}
}
Reference — Python.
import sys
def main() -> None:
parts = sys.stdin.read().split()
v = int(parts[0])
e = int(parts[1])
kind = parts[2]
matrix = 4 * v * v
list_bytes = 4 * e + 40 * v
edge = 8 * e
csr = 4 * (v + 1) + 4 * e
out = [
f"matrix={matrix}",
f"list={list_bytes}",
f"edge={edge}",
f"csr={csr}",
]
if v <= 5000 and e >= v * v // 4:
choice = "matrix"
elif kind == "static":
choice = "csr"
else:
choice = "list"
out.append(f"choice={choice}")
sys.stdout.write("\n".join(out) + "\n")
if __name__ == "__main__":
main()
Evaluation criteria. - All byte counts use 64-bit arithmetic (no overflow at V = 2*10^8). - A small dense graph picks matrix; a large static sparse graph picks csr; a large mutable sparse graph picks list. - The four byte estimates match the stated model exactly.
Benchmark Task¶
Task B — Representation throughput across Go, Java, Python¶
Problem. For each language, write a self-contained benchmark over a random directed graph of V vertices and E edges. Measure three workloads that stress the representation differently:
- (a) build — construct an adjacency list (
List<List<Integer>>/[][]int/ list of lists) from the edge array. - (b) build CSR — construct a CSR (
offset,target) from the same edge array via the two-pass algorithm. - (c) full scan — sum every neighbor id by traversing the CSR, then separately by traversing the adjacency list; report both scan times.
Run for (V, E) ∈ {(10^4, 5*10^4), (10^5, 5*10^5), (10^6, 5*10^6)}. Repeat each measurement 5 times and report the mean wall-clock time in milliseconds. Use the same seed across languages so the input distribution is identical.
Input / Output spec. - No stdin input. Output a fixed table to stdout:
V E build_list_ms build_csr_ms scan_csr_ms scan_list_ms
10000 50000 ... ... ... ...
100000 500000 ... ... ... ...
1000000 5000000 ... ... ... ...
Constraints. - Seed: 42. - Edge endpoints: uniform in [0, V). - Time only the workload, not the random generation.
Reference — Go.
package main
import (
"fmt"
"math/rand"
"time"
)
func genEdges(v, e int, seed int64) [][2]int {
r := rand.New(rand.NewSource(seed))
edges := make([][2]int, e)
for i := range edges {
edges[i] = [2]int{r.Intn(v), r.Intn(v)}
}
return edges
}
func buildList(v int, edges [][2]int) [][]int {
adj := make([][]int, v)
for _, e := range edges {
adj[e[0]] = append(adj[e[0]], e[1])
}
return adj
}
func buildCSR(v int, edges [][2]int) ([]int, []int) {
offset := make([]int, v+1)
for _, e := range edges {
offset[e[0]+1]++
}
for i := 1; i <= v; i++ {
offset[i] += offset[i-1]
}
target := make([]int, len(edges))
cursor := make([]int, v)
copy(cursor, offset[:v])
for _, e := range edges {
target[cursor[e[0]]] = e[1]
cursor[e[0]]++
}
return offset, target
}
func meanMs(d []time.Duration) float64 {
var s int64
for _, x := range d {
s += x.Microseconds()
}
return float64(s) / float64(len(d)) / 1000.0
}
func main() {
cases := [][2]int{{10000, 50000}, {100000, 500000}, {1000000, 5000000}}
fmt.Println("V E build_list_ms build_csr_ms scan_csr_ms scan_list_ms")
for _, c := range cases {
v, e := c[0], c[1]
edges := genEdges(v, e, 42)
var bl, bc, sc, sl []time.Duration
for i := 0; i < 5; i++ {
t := time.Now()
adj := buildList(v, edges)
bl = append(bl, time.Since(t))
t = time.Now()
offset, target := buildCSR(v, edges)
bc = append(bc, time.Since(t))
t = time.Now()
var s1 int64
for k := range target {
s1 += int64(target[k])
}
sc = append(sc, time.Since(t))
t = time.Now()
var s2 int64
for u := 0; u < v; u++ {
for _, w := range adj[u] {
s2 += int64(w)
}
}
sl = append(sl, time.Since(t))
_ = offset
_ = s1
_ = s2
}
fmt.Printf("%-9d %-10d %-15.2f %-14.2f %-13.2f %-12.2f\n",
v, e, meanMs(bl), meanMs(bc), meanMs(sc), meanMs(sl))
}
}
Reference — Java.
import java.util.*;
public class TaskB {
static int[][] genEdges(int v, int e, long seed) {
Random r = new Random(seed);
int[][] edges = new int[e][2];
for (int i = 0; i < e; i++) {
edges[i][0] = r.nextInt(v);
edges[i][1] = r.nextInt(v);
}
return edges;
}
static List<List<Integer>> buildList(int v, int[][] edges) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < v; i++) adj.add(new ArrayList<>());
for (int[] e : edges) adj.get(e[0]).add(e[1]);
return adj;
}
static int[][] buildCSR(int v, int[][] edges) {
int[] offset = new int[v + 1];
for (int[] e : edges) offset[e[0] + 1]++;
for (int i = 1; i <= v; i++) offset[i] += offset[i - 1];
int[] target = new int[edges.length];
int[] cursor = Arrays.copyOf(offset, v);
for (int[] e : edges) target[cursor[e[0]]++] = e[1];
return new int[][]{offset, target};
}
static double meanMs(long[] ns) {
long s = 0;
for (long x : ns) s += x;
return (s / 1_000_000.0) / ns.length;
}
public static void main(String[] args) {
int[][] cases = {{10000, 50000}, {100000, 500000}, {1000000, 5000000}};
System.out.println("V E build_list_ms build_csr_ms scan_csr_ms scan_list_ms");
for (int[] c : cases) {
int v = c[0], e = c[1];
int[][] edges = genEdges(v, e, 42L);
long[] bl = new long[5], bc = new long[5], sc = new long[5], sl = new long[5];
for (int i = 0; i < 5; i++) {
long t = System.nanoTime();
List<List<Integer>> adj = buildList(v, edges);
bl[i] = System.nanoTime() - t;
t = System.nanoTime();
int[][] csr = buildCSR(v, edges);
bc[i] = System.nanoTime() - t;
t = System.nanoTime();
long s1 = 0;
for (int x : csr[1]) s1 += x;
sc[i] = System.nanoTime() - t;
t = System.nanoTime();
long s2 = 0;
for (int u = 0; u < v; u++) for (int w : adj.get(u)) s2 += w;
sl[i] = System.nanoTime() - t;
if (s1 + s2 == -1) System.out.print("");
}
System.out.printf("%-9d %-10d %-15.2f %-14.2f %-13.2f %-12.2f%n",
v, e, meanMs(bl), meanMs(bc), meanMs(sc), meanMs(sl));
}
}
}
Reference — Python.
import random
import time
from typing import List, Tuple
def gen_edges(v: int, e: int, seed: int) -> List[Tuple[int, int]]:
r = random.Random(seed)
return [(r.randrange(v), r.randrange(v)) for _ in range(e)]
def build_list(v: int, edges):
adj = [[] for _ in range(v)]
for u, w in edges:
adj[u].append(w)
return adj
def build_csr(v: int, edges):
offset = [0] * (v + 1)
for u, _ in edges:
offset[u + 1] += 1
for i in range(1, v + 1):
offset[i] += offset[i - 1]
target = [0] * len(edges)
cursor = offset[:v]
for u, w in edges:
target[cursor[u]] = w
cursor[u] += 1
return offset, target
def mean_ms(samples: List[float]) -> float:
return sum(samples) / len(samples) * 1000.0
def main() -> None:
cases = [(10_000, 50_000), (100_000, 500_000), (1_000_000, 5_000_000)]
print("V E build_list_ms build_csr_ms scan_csr_ms scan_list_ms")
for v, e in cases:
edges = gen_edges(v, e, 42)
bl, bc, sc, sl = [], [], [], []
for _ in range(5):
t = time.perf_counter()
adj = build_list(v, edges)
bl.append(time.perf_counter() - t)
t = time.perf_counter()
offset, target = build_csr(v, edges)
bc.append(time.perf_counter() - t)
t = time.perf_counter()
s1 = 0
for x in target:
s1 += x
sc.append(time.perf_counter() - t)
t = time.perf_counter()
s2 = 0
for u in range(v):
for w in adj[u]:
s2 += w
sl.append(time.perf_counter() - t)
print(f"{v:<9d} {e:<10d} {mean_ms(bl):<15.2f} {mean_ms(bc):<14.2f} "
f"{mean_ms(sc):<13.2f} {mean_ms(sl):<12.2f}")
if __name__ == "__main__":
main()
Evaluation criteria. - Same seed produces the same edge set across languages. - The CSR scan (scan_csr_ms) is at least as fast as the list scan (scan_list_ms), and the gap widens with V — the cache-locality effect. - The benchmark does not time edge generation. - Writeup: a short note on which language showed the widest CSR-vs-list scan gap, and why (cache locality versus interpreter overhead).