const std = @import("std"); fn StackList(comptime T: type, comptime capacity_type: type, comptime capacity: capacity_type) type { return struct { const Self = @This(); mem: [capacity]T, length: capacity_type, fn add(self: *Self, value: T) void { self.mem[self.length] = value; self.length += 1; } fn has(self: *Self, needle: T) bool { for (0..self.length) |i| { if (self.mem[i] == needle) { return true; } } return false; } fn getMutableSlice(self: *Self) []T { return (&self.mem)[0..self.length]; } fn getSlice(self: *const Self) []const T { return self.mem[0..self.length]; } fn init() Self { return Self{ .mem = undefined, .length = 0, }; } }; } fn readNumber(comptime T: type, line: []const u8, index: *usize) T { var result: T = 0; while (index.* < line.len) : (index.* += 1) { const char = line[index.*]; switch (char) { '0'...'9' => { result = result * 10 + (char - '0'); }, else => { break; }, } } return result; } const Segment = packed struct(u32) { first: u16, last: u16, fn init(a: u16, b: u16) Segment { if (a < b) { return .{ .first = a, .last = b, }; } else { return .{ .first = b, .last = a, }; } } fn intersects(a: Segment, b: Segment) bool { return a.first <= b.last and a.last >= b.first; } }; const Brick = struct { x: Segment, y: Segment, z: Segment, is_settled: bool, fn is_above(self: *const Brick, other: Brick) bool { return Segment.intersects(self.x, other.x) and Segment.intersects(self.y, other.y) and self.z.first > other.z.first; } fn is_on(self: *const Brick, other: Brick) bool { return Segment.intersects(self.x, other.x) and Segment.intersects(self.y, other.y) and self.z.first == other.z.last + 1; } pub fn format(value: Brick, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { return writer.print("{d},{d},{d}~{d},{d},{d}", .{ value.x.first, value.y.first, value.z.first, value.x.last, value.y.last, value.z.last }); } }; fn settleBrick(bricks: []Brick, this: *Brick) usize { if (this.is_settled) { return 0; } var moved_bricks: usize = 0; var min_z: u16 = 1; for (bricks) |*other| { if (this.is_above(other.*)) { moved_bricks += settleBrick(bricks, other); min_z = @max(min_z, other.z.last + 1); } } if (this.z.first != min_z) { moved_bricks += 1; } this.z.last -= (this.z.first - min_z); this.z.first = min_z; this.is_settled = true; return moved_bricks; } fn settleBricks(bricks: []Brick) usize { var result: usize = 0; for (bricks) |*this| { result += settleBrick(bricks, this); } return result; } fn simulateDisintegration(bricks: []const Brick, brick_number: usize) usize { var simulated_bricks_array: [1500]Brick = undefined; std.mem.copy(Brick, &simulated_bricks_array, bricks); var simulated_bricks = simulated_bricks_array[0..bricks.len]; for (simulated_bricks) |*brick| { brick.*.is_settled = false; } simulated_bricks[brick_number].z = Segment.init(0, 0); simulated_bricks[brick_number].is_settled = true; return settleBricks(simulated_bricks); } fn solveForBricks(bricks: []Brick) usize { _ = settleBricks(bricks); var result: usize = 0; for (0..bricks.len) |brick_number| { //std.debug.print("Simulating disintegration of brick {d}\n", .{brick_number}); result += simulateDisintegration(bricks, brick_number); } return result; } fn solveLines(lines: []const []const u8) usize { var bricks_list = StackList(Brick, usize, 1500).init(); for (lines) |line| { var i: usize = 0; var x1 = readNumber(u16, line, &i); i += 1; var y1 = readNumber(u16, line, &i); i += 1; var z1 = readNumber(u16, line, &i); i += 1; var x2 = readNumber(u16, line, &i); i += 1; var y2 = readNumber(u16, line, &i); i += 1; var z2 = readNumber(u16, line, &i); bricks_list.add(.{ .x = Segment.init(x1, x2), .y = Segment.init(y1, y2), .z = Segment.init(z1, z2), .is_settled = false, }); } return solveForBricks(bricks_list.getMutableSlice()); } pub fn solveAll(reader: anytype) !usize { var result: usize = 0; while (true) { var allocator_buffer: [50000]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&allocator_buffer); var allocator = fba.allocator(); var lines = StackList([]u8, usize, 1500).init(); var empty_line_reached = false; var line_buffer: [1000]u8 = undefined; while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| { if (line.len == 0) { empty_line_reached = true; break; } lines.add(try allocator.dupe(u8, line)); } result += solveLines(lines.getSlice()); if (!empty_line_reached) { return result; } } } pub fn main() !void { const stdout = std.io.getStdOut().writer(); const raw_in = std.io.getStdIn(); var buffered_reader = std.io.bufferedReader(raw_in.reader()); var reader = buffered_reader.reader(); const result = try solveAll(&reader); try stdout.print("{d}\n", .{result}); }