@@ -28,14 +28,19 @@ public object PartTwo(string input) {
28
28
29
29
// Priority queue based maximum search with LOTS OF PRUNING
30
30
private int MaxGeodes ( Blueprint blueprint , int timeLimit ) {
31
- var q = new PriorityQueue < ( State state , Robot [ ] ignore ) , int > ( ) ;
31
+ var q = new PriorityQueue < State , int > ( ) ;
32
32
var seen = new HashSet < State > ( ) ;
33
33
34
- enqueue ( new State ( remainingTime : timeLimit , available : Nothing , producing : Ore ) ) ;
34
+ enqueue ( new State (
35
+ remainingTime : timeLimit ,
36
+ available : Nothing ,
37
+ producing : Ore ,
38
+ dontBuild : 0
39
+ ) ) ;
35
40
36
41
var max = 0 ;
37
42
while ( q . Count > 0 ) {
38
- var ( state , ignore ) = q . Dequeue ( ) ;
43
+ var state = q . Dequeue ( ) ;
39
44
40
45
// Queue is ordered by potentialGeodeCount, there is
41
46
// no point in investigating the remaining items.
@@ -50,34 +55,34 @@ private int MaxGeodes(Blueprint blueprint, int timeLimit) {
50
55
// time is off, just update max
51
56
max = Math . Max ( max , state . available . geode ) ;
52
57
} else {
53
-
54
- // what robots can be created from our available materials?
58
+ // What robots can be created from the available materials?
55
59
var buildableRobots = blueprint . robots
56
60
. Where ( robot => state . available >= robot . cost )
57
61
. ToArray ( ) ;
58
62
59
- // 1) wait until next round for potentialy more robot types
60
- enqueue (
61
- state with {
62
- remainingTime = state . remainingTime - 1 ,
63
- available = state . available + state . producing ,
64
- } ,
65
- // if we have materials for some robot, there is no point
66
- // in building it only in the next round let's ignore these
67
- ignore : buildableRobots
68
- ) ;
69
-
70
- // 2) or build some robots right away
63
+ // 1) build one of them right away
71
64
foreach ( var robot in buildableRobots ) {
72
-
73
- if ( ! ignore . Contains ( robot ) && worthBuilding ( state , robot ) ) {
65
+ if ( worthBuilding ( state , robot ) ) {
74
66
enqueue ( state with {
75
67
remainingTime = state . remainingTime - 1 ,
76
68
available = state . available + state . producing - robot . cost ,
77
69
producing = state . producing + robot . producing ,
70
+ dontBuild = 0
78
71
} ) ;
79
72
}
80
73
}
74
+
75
+ // 2) or wait until next round for more robot types. Don't postpone
76
+ // building of robots which are already available. This is a very
77
+ // very important prunning step. It's about 25 times faster if we
78
+ // do it this way.
79
+ enqueue (
80
+ state with {
81
+ remainingTime = state . remainingTime - 1 ,
82
+ available = state . available + state . producing ,
83
+ dontBuild = buildableRobots . Select ( robot => robot . id ) . Sum ( ) ,
84
+ }
85
+ ) ;
81
86
}
82
87
}
83
88
}
@@ -86,35 +91,43 @@ state with {
86
91
87
92
// -------
88
93
89
- // Upper limit for the maximum geodes we can mine starting from this state.
90
- // Let's be optimistic and suppose that in each and every step we will be able to build a new geode robot...
94
+ // Upper limit for the maximum geodes we reach when starting from this state.
95
+ // Let's be optimistic and suppose that in each step we will be able to build
96
+ // a new geode robot...
91
97
int potentialGeodeCount ( State state ) {
92
- var future = ( state . producing . geode + state . producing . geode + state . remainingTime ) * state . remainingTime / 2 ;
98
+ // sum of [state.producing.geode .. state.producing.geode + state.remainingTime - 1]
99
+ var future = ( 2 * state . producing . geode + state . remainingTime - 1 ) * state . remainingTime / 2 ;
93
100
return state . available . geode + future ;
94
101
}
95
102
96
- // We can build just a single robot in a round. This gives as a prunning condition.
97
- // Producing more material in a round that we can spend on building a new robot is worthless.
98
103
bool worthBuilding ( State state , Robot robot ) {
104
+ // We can explicitly ignore building some robots.
105
+ // Robot ids are powers of 2 used as flags in the dontBuild integer.
106
+ if ( ( state . dontBuild & robot . id ) != 0 ) {
107
+ return false ;
108
+ }
109
+
110
+ // Our factory can build just a single robot in a round. This gives as
111
+ // a prunning condition. Producing more material in a round that we can
112
+ // spend on building a new robot is worthless.
99
113
return state . producing + robot . producing <= blueprint . maxCost ;
100
114
}
101
115
102
116
// Just add an item to the search queue, use -potentialGeodeCount as priority
103
- void enqueue ( State state , Robot [ ] ignore = null ) {
104
- q . Enqueue ( ( state , ignore ?? new Robot [ 0 ] ) , - potentialGeodeCount ( state ) ) ;
117
+ void enqueue ( State state ) {
118
+ q . Enqueue ( state , - potentialGeodeCount ( state ) ) ;
105
119
}
106
-
107
120
}
108
121
109
122
IEnumerable < Blueprint > Parse ( string input ) {
110
123
foreach ( var line in input . Split ( "\n " ) ) {
111
124
var numbers = Regex . Matches ( line , @"(\d+)" ) . Select ( x => int . Parse ( x . Value ) ) . ToArray ( ) ;
112
125
yield return new Blueprint (
113
126
id : numbers [ 0 ] ,
114
- new Robot ( producing : Ore , cost : numbers [ 1 ] * Ore ) ,
115
- new Robot ( producing : Clay , cost : numbers [ 2 ] * Ore ) ,
116
- new Robot ( producing : Obsidian , cost : numbers [ 3 ] * Ore + numbers [ 4 ] * Clay ) ,
117
- new Robot ( producing : Geode , cost : numbers [ 5 ] * Ore + numbers [ 6 ] * Obsidian )
127
+ new Robot ( id : 1 , producing : Ore , cost : numbers [ 1 ] * Ore ) ,
128
+ new Robot ( id : 2 , producing : Clay , cost : numbers [ 2 ] * Ore ) ,
129
+ new Robot ( id : 4 , producing : Obsidian , cost : numbers [ 3 ] * Ore + numbers [ 4 ] * Clay ) ,
130
+ new Robot ( id : 8 , producing : Geode , cost : numbers [ 5 ] * Ore + numbers [ 6 ] * Obsidian )
118
131
) ;
119
132
}
120
133
}
@@ -164,8 +177,8 @@ record Material(int ore, int clay, int obsidian, int geode) {
164
177
}
165
178
}
166
179
167
- record Robot ( Material cost , Material producing ) ;
168
- record State ( int remainingTime , Material available , Material producing ) ;
180
+ record Robot ( int id , Material cost , Material producing ) ;
181
+ record State ( int remainingTime , Material available , Material producing , int dontBuild ) ;
169
182
record Blueprint ( int id , params Robot [ ] robots ) {
170
183
public Material maxCost = new Material (
171
184
ore : robots . Select ( robot => robot . cost . ore ) . Max ( ) ,
0 commit comments