-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path16.cs
196 lines (161 loc) · 6.16 KB
/
16.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
var graph = ReadInput(Console.In);
// There are multiple tricks for part 1:
// * Simplify the graph by deleting nodes with zero flow (except AA). They all have only one edge in/out.
// * Use a heuristic to discard search paths that cannot possibly lead to a higher than current highest total flow.
var minEdgeWeight = graph.SelectMany(n => n.Next).Select(e => e.Weight).Min();
Console.WriteLine($"Searching graph of {graph.Count} nodes, min edge weight {minEdgeWeight}");
var sw = System.Diagnostics.Stopwatch.StartNew();
var answer = FindMaxFlow(graph, 30);
Console.WriteLine($"Max flow: {answer} (elapsed: {sw.Elapsed})");
// The trick for part two is to simply try every single partition of nodes into two sub-sets, simulating the
// maximum flow possible for each set in 26 steps. There are only 2^14 possible combinations to calculate, as the
// input has 15 nodes with non-zero flow, and we can cut that in half because of symmetry.
List<Node> a = new(), b = new();
var combinations = 1u << graph.Count - 2;
Console.WriteLine($"Testing {combinations} work partitions.");
sw = System.Diagnostics.Stopwatch.StartNew();
for (uint k = 0; k < combinations; ++k)
{
var mask = k << 2 | 2;
a.Add(graph[0]);
b.Add(graph[0]);
for (var i = 1; i < graph.Count; ++i)
{
if (((mask >> i) & 1) == 1)
{
a.Add(graph[i]);
}
else
{
b.Add(graph[i]);
}
}
var aFlow = FindMaxFlow(a, 26);
var bFlow = FindMaxFlow(b, 26);
answer = Math.Max(answer, aFlow + bFlow);
a.Clear();
b.Clear();
}
Console.WriteLine($"Max flow with help: {answer} (elapsed: {sw.Elapsed})");
List<Node> ReadInput(TextReader reader)
{
Dictionary<string, Node> nodes = new();
while (reader.ReadLine()?.Split() is { } line)
{
Node GetOrCreateNode(string name)
=> nodes.TryGetValue(name, out var n) ? n : nodes[name] = new(name);
var flow = int.Parse(line[4].Substring(5, line[4].Length - 6));
var node = GetOrCreateNode(line[1]);
node.Flow = flow;
var targets = line[9..].Select(l => l.TrimEnd(','));
node.Next.AddRange(targets.Select(t => new Edge(GetOrCreateNode(t), 1)));
}
return DeleteZeroNodes(nodes["AA"]);
}
List<Node> DeleteZeroNodes(Node start)
{
List<Node> nodes = new();
bool TryFindZero(out Node zero)
{
nodes.Clear();
Queue<Node> queue = new();
queue.Enqueue(start);
while (queue.TryDequeue(out var node))
{
if (!nodes.Contains(node))
{
nodes.Add(node);
}
if (node.Flow == 0 && !node.Equals(start))
{
zero = node;
return true;
}
foreach (var edge in node.Next.Where(edge => !nodes.Contains(edge.Target)))
{
queue.Enqueue(edge.Target);
}
}
zero = start;
return false;
}
while (TryFindZero(out var zero))
{
// All zero nodes (except start) has only two edges.
var weight = zero.Next[0].Weight + zero.Next[1].Weight;
Node p = zero.Next[0].Target, q = zero.Next[1].Target;
var i = p.Next.FindIndex(e => e.Target.Equals(zero));
var j = q.Next.FindIndex(e => e.Target.Equals(zero));
p.Next[i] = new(q, weight);
q.Next[j] = new(p, weight);
}
return nodes;
}
int FindMaxFlow(List<Node> nodes, int steps)
{
var withFlow = nodes.Where(n => n.Flow != 0).ToHashSet();
return Search(nodes[0], 0, new HashSet<Node>(), 0, 0);
int Search(Node node, int step, HashSet<Node> opened, int currentFlow, int maxTotalFlow)
{
if (step >= steps || opened.Count == withFlow.Count)
{
return currentFlow;
}
// Early exit if we know an upper bound on remaining flow is still less that current max solution.
int heuristic = 0, futureStep = step;
foreach (var n in nodes.Where(n => !opened.Contains(n)).OrderByDescending(n => n.Flow))
{
if (futureStep >= steps) break;
heuristic += (steps - futureStep - 1) * n.Flow;
// Spend at least minEdgeWeight minute moving to next node, and 1 minute opening.
futureStep += minEdgeWeight + 1; // Real input has no edge less than cost 2.
}
if (currentFlow + heuristic < maxTotalFlow)
{
return heuristic;
}
// Open valve and go to next.
if (!opened.Contains(node) && withFlow.Contains(node))
{
opened.Add(node);
var flow = (steps - step - 1) * node.Flow; // Total contribution from this valve until the end.
foreach (var edge in node.Next
.OrderBy(n => opened.Contains(n.Target))
.ThenByDescending(n => n.Target.Flow))
{
var branchTotalFlow = Search(edge.Target, step + 1 + edge.Weight, opened, currentFlow + flow, maxTotalFlow);
maxTotalFlow = Math.Max(branchTotalFlow, maxTotalFlow);
}
opened.Remove(node);
}
// Skip valve and go to next immediately.
foreach (var edge in node.Next
.OrderBy(n => opened.Contains(n.Target))
.ThenByDescending(n => n.Target.Flow))
{
var branchTotalFlow = Search(edge.Target, step + edge.Weight, opened, currentFlow, maxTotalFlow);
maxTotalFlow = Math.Max(branchTotalFlow, maxTotalFlow);
}
return maxTotalFlow;
}
}
internal sealed record Edge(Node Target, int Weight);
internal sealed class Node : IEquatable<Node>
{
public string Name { get; }
public int Flow { get; set; }
public List<Edge> Next { get; }
public Node(string name)
{
Name = name;
Next = new();
}
public bool Equals(Node? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Name == other.Name;
}
public override bool Equals(object? obj) => ReferenceEquals(this, obj) || obj is Node other && Equals(other);
public override int GetHashCode() => Name.GetHashCode();
}