Skip to content

Commit f77d9db

Browse files
committed
Day 25 🎄
1 parent 6277a6a commit f77d9db

File tree

4 files changed

+234
-0
lines changed

4 files changed

+234
-0
lines changed

‎src/main/kotlin/common/datastructures/DisjointSets.kt‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ class DisjointSets<T> {
44
val rank: MutableMap<T, Int> = mutableMapOf()
55
val parent: MutableMap<T, T> = mutableMapOf()
66

7+
val size get() = rank.size
8+
val sets: MutableSet<T> = mutableSetOf()
9+
710
private fun checkInit(x: T) {
811
if (x in rank) return
912
rank[x] = 0
1013
parent[x] = x
14+
sets += x
1115
}
1216

1317
fun find(x: T): T {
@@ -26,11 +30,14 @@ class DisjointSets<T> {
2630
}
2731
if (rank[rootX]!! < rank[rootY]!!) {
2832
parent[rootX] = rootY
33+
sets -= rootX
2934
} else if (rank[rootY]!! < rank[rootX]!!) {
3035
parent[rootY] = rootX
36+
sets -= rootY
3137
} else {
3238
parent[rootY] = rootX
3339
rank[rootX] = rank[rootX]!! + 1
40+
sets -= rootY
3441
}
3542
}
3643
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
@file:Suppress("UnstableApiUsage")
2+
3+
package common.datastructures.flow
4+
5+
import com.google.common.graph.ValueGraph
6+
import java.util.*
7+
8+
object Dinics {
9+
fun <T> maxFlow(graph: ValueGraph<T, Int>, src: T, sink: T): Int {
10+
return maxFlowGraph(graph, src, sink).first
11+
}
12+
13+
data class MinCut<T>(
14+
val srcSet: Set<T>,
15+
val cut: List<Pair<T, T>>,
16+
)
17+
18+
fun <T> minCut(graph: ValueGraph<T, Int>, src: T, sink: T): MinCut<T> {
19+
val (_, nodes) = maxFlowGraph(graph, src, sink)
20+
21+
val reachable = mutableSetOf<T>()
22+
val queue = LinkedList<Node<T>>().also { it.add(nodes[src]!!) }
23+
while (queue.isNotEmpty()) {
24+
val cur = queue.removeFirst()
25+
if (cur.node in reachable) {
26+
continue
27+
}
28+
reachable += cur.node
29+
30+
cur.edges.forEach { edge ->
31+
if (edge.flow == edge.capacity) {
32+
return@forEach
33+
}
34+
35+
queue += edge.to
36+
}
37+
}
38+
39+
val cut = graph.edges().mapNotNull { edge ->
40+
val isMinCut = edge.nodeU() in reachable != edge.nodeV() in reachable
41+
if (isMinCut) {
42+
return@mapNotNull edge.nodeU() to edge.nodeV()
43+
}
44+
return@mapNotNull null
45+
}
46+
47+
return MinCut(reachable, cut)
48+
}
49+
50+
private fun <T> maxFlowGraph(graph: ValueGraph<T, Int>, src: T, sink: T): Pair<Int, Map<T, Node<T>>> {
51+
val nodes = graph.toFlowGraph()
52+
val srcNode = nodes[src]!!
53+
val sinkNode = nodes[sink]!!
54+
var maxFlow = 0
55+
while (levelGraph(nodes, srcNode, sinkNode)) {
56+
var flow: Int
57+
do {
58+
flow = sendFlow(srcNode, sinkNode, Int.MAX_VALUE)
59+
maxFlow += flow
60+
} while (flow > 0)
61+
}
62+
return maxFlow to nodes
63+
}
64+
65+
class Node<T>(
66+
val node: T,
67+
var level: Int,
68+
var edges: List<Edge<T>>,
69+
)
70+
data class Edge<T>(
71+
val to: Node<T>,
72+
val capacity: Int,
73+
var flow: Int,
74+
) {
75+
lateinit var reverseEdge: Edge<T>
76+
}
77+
78+
private fun <T> sendFlow(from: Node<T>, sink: Node<T>, flow: Int): Int {
79+
if (from == sink) {
80+
return flow
81+
}
82+
from.edges.forEach { edge ->
83+
if (edge.to.level == from.level + 1 && edge.capacity > edge.flow) {
84+
val minFlow = minOf(flow, edge.capacity - edge.flow)
85+
val resultFlow = sendFlow(edge.to, sink, minFlow)
86+
if (resultFlow > 0) {
87+
edge.flow += resultFlow
88+
edge.reverseEdge.flow -= resultFlow
89+
return resultFlow
90+
}
91+
}
92+
}
93+
return 0
94+
}
95+
96+
private fun <T> ValueGraph<T & Any, Int>.toFlowGraph(): Map<T, Node<T>> {
97+
val nodes = nodes().toList().associateWith { node ->
98+
Node(node, -1, emptyList())
99+
}
100+
101+
fun addEdge(from: Node<T>, to: Node<T>, capacity: Int) {
102+
val edge = Edge(to, capacity, 0)
103+
val reverse = Edge(from, capacity, capacity)
104+
edge.reverseEdge = reverse
105+
reverse.reverseEdge = edge
106+
from.edges += edge
107+
to.edges += reverse
108+
}
109+
110+
edges().forEach { edge ->
111+
val from = nodes[edge.nodeU()]!!
112+
val to = nodes[edge.nodeV()]!!
113+
val value = edgeValue(edge).get()
114+
addEdge(from, to, value)
115+
if (!edge.isOrdered) {
116+
addEdge(to, from, value)
117+
}
118+
}
119+
120+
return nodes
121+
}
122+
123+
private fun <T> levelGraph(nodes: Map<T, Node<T>>, src: Node<T>, sink: Node<T>): Boolean {
124+
nodes.values.forEach { it.level = -1 }
125+
src.level = 0
126+
127+
val queue = LinkedList<Node<T>>().also { it.add(src) }
128+
while (queue.isNotEmpty()) {
129+
val cur = queue.removeFirst()
130+
for (e in cur.edges) {
131+
if (e.flow < e.capacity && e.to.level == -1) {
132+
e.to.level = cur.level + 1
133+
queue += e.to
134+
}
135+
}
136+
}
137+
138+
return sink.level > 0
139+
}
140+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@file:Suppress("UnstableApiUsage")
2+
3+
package y23
4+
5+
import com.google.common.graph.*
6+
import common.puzzle.solvePuzzle
7+
import common.puzzle.Input
8+
import common.puzzle.Puzzle
9+
import common.datastructures.*
10+
import common.datastructures.flow.Dinics
11+
import common.ext.*
12+
import common.util.*
13+
import java.util.*
14+
import kotlin.math.*
15+
import kotlin.system.exitProcess
16+
17+
18+
fun main() = solvePuzzle(year = 2023, day = 25) { Day25(it) }
19+
20+
class Day25(val input: Input) : Puzzle {
21+
22+
private fun createGraph(lines: List<String>): MutableValueGraph<String, Int> {
23+
val graph = ValueGraphBuilder.undirected()
24+
.build<String, Int>()
25+
lines.forEach { line ->
26+
val from = line.substringBefore(":")
27+
val to = line.substringAfter(": ").split(" ")
28+
to.forEach { graph.putEdgeValue(from, it, 1) }
29+
}
30+
return graph
31+
}
32+
33+
override fun solveLevel1(): Any {
34+
val graph = createGraph(input.lines)
35+
val nodes = graph.nodes().toList()
36+
for (a in nodes.indices) {
37+
for (b in a + 1 until nodes.size) {
38+
val maxFlow = Dinics.maxFlow(graph, nodes[a], nodes[b])
39+
if (maxFlow == 3) {
40+
val (srcSet, _) = Dinics.minCut(graph, nodes[a], nodes[b])
41+
return srcSet.size * (nodes.size - srcSet.size)
42+
}
43+
}
44+
}
45+
46+
error("did not find cut of size 3")
47+
}
48+
49+
override fun solveLevel2(): Any {
50+
return 42
51+
}
52+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package y23
2+
3+
import common.puzzle.Input
4+
import org.junit.jupiter.api.Assertions.assertEquals
5+
import org.junit.jupiter.api.Test
6+
7+
internal class Day25Test {
8+
private val sample = Input("""
9+
jqt: rhn xhk nvd
10+
rsh: frs pzl lsr
11+
xhk: hfx
12+
cmg: qnr nvd lhk bvb
13+
rhn: xhk bvb hfx
14+
bvb: xhk hfx
15+
pzl: lsr hfx nvd
16+
qnr: nvd
17+
ntq: jqt hfx bvb xhk
18+
nvd: lhk
19+
lsr: lhk
20+
rzs: qnr cmg lsr rsh
21+
frs: qnr lhk lsr
22+
""".trimIndent())
23+
24+
private val day = Day25(sample)
25+
26+
@Test
27+
fun solveLevel1() {
28+
assertEquals(54, day.solveLevel1())
29+
}
30+
31+
@Test
32+
fun solveLevel2() {
33+
assertEquals(42, day.solveLevel2())
34+
}
35+
}

0 commit comments

Comments
 (0)