├── .gitignore ├── CMakeLists.txt ├── README.md ├── input └── README.md └── src ├── advent2021.h ├── bits.h ├── day01.cpp ├── day02.cpp ├── day03.cpp ├── day04.cpp ├── day05.cpp ├── day06.cpp ├── day07.cpp ├── day08.cpp ├── day09.cpp ├── day10.cpp ├── day11.cpp ├── day12.cpp ├── day13.cpp ├── day14.cpp ├── day15.cpp ├── day16.cpp ├── day17.cpp ├── day18.cpp ├── day19.cpp ├── day20.cpp ├── day21.cpp ├── day22.cpp ├── day23.cpp ├── day24.cpp ├── day25.cpp ├── main.cpp ├── numeric.h ├── ocr.h └── parse.h /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | *.cmake 4 | Makefile 5 | advent2021 6 | *.sw? 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | project (advent2021) 3 | 4 | set(CMAKE_BUILD_TYPE Release) 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -std=c++20") 6 | 7 | set(CMAKE_C_COMPILER "clang" CACHE STRING "clang compiler" FORCE) 8 | set(CMAKE_CXX_COMPILER "clang++" CACHE STRING "clang++ compiler" FORCE) 9 | 10 | add_executable(advent2021 11 | src/main.cpp 12 | src/day01.cpp 13 | src/day02.cpp 14 | src/day03.cpp 15 | src/day04.cpp 16 | src/day05.cpp 17 | src/day06.cpp 18 | src/day07.cpp 19 | src/day08.cpp 20 | src/day09.cpp 21 | src/day10.cpp 22 | src/day11.cpp 23 | src/day12.cpp 24 | src/day13.cpp 25 | src/day14.cpp 26 | src/day15.cpp 27 | src/day16.cpp 28 | src/day17.cpp 29 | src/day18.cpp 30 | src/day19.cpp 31 | src/day20.cpp 32 | src/day21.cpp 33 | src/day22.cpp 34 | src/day23.cpp 35 | src/day24.cpp 36 | src/day25.cpp 37 | ) 38 | target_link_libraries(advent2021) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # advent2021-fast 2 | 3 | **These solutions are a work in progress.** 4 | 5 | [Advent of Code 2021](https://adventofcode.com/2021/) optimized C++ solutions. 6 | 7 | Here are the timings from an example run on an i9-9980HK CPU laptop. Times will vary depending on inputs. 8 | 9 | Day 01 44 μs 10 | Day 02 4 μs 11 | Day 03 8 μs 12 | Day 04 14 μs 13 | Day 05 53 μs 14 | Day 06 2 μs 15 | Day 07 9 μs 16 | Day 08 28 μs 17 | Day 09 68 μs 18 | Day 10 19 μs 19 | Day 11 11 μs 20 | Day 12 20 μs 21 | Day 13 28 μs 22 | Day 14 5 μs 23 | Day 15 4047 μs 24 | Day 16 8 μs 25 | Day 17 1 μs 26 | Day 18 1344 μs 27 | Day 19 1220 μs 28 | Day 20 304 μs 29 | Day 21 310 μs 30 | Day 22 742 μs 31 | Day 23 277 μs 32 | Day 24 2 μs 33 | Day 25 393 μs 34 | ----------------- 35 | Total: 8961 μs 36 | 37 | Solutions should work with any puzzle input, provided it is byte-for-byte an exact copy of the file downloaded from Advent of Code. 38 | 39 | # Summary of solutions 40 | 41 | Here are a few brief notes about each solution. 42 | 43 | ## Day 1 44 | 45 | The input numbers are small enough to fit in a 16-bit integer. Because we need to examine only a small sliding window, we can pack the four most recently seen values into a 64-bit integer and use bit shifting to advance the window. 46 | 47 | // Using bit shift 48 | previous = (previous << 16) + n; 49 | 50 | // Equivalent version 51 | previous[3] = previous[2] 52 | previous[2] = previous[1] 53 | previous[1] = previous[0] 54 | previous[0] = n 55 | 56 | ## Day 2 57 | 58 | To parse the commands `down` `up` and `forward`, we only need to test if the first letter is `d` `u` or `f`. 59 | 60 | ## Day 3 61 | 62 | Each of the 12-bit input numbers is unique, so they can be sorted by creating and iterating over a 4096-element bitset. Part 2 uses binary search to find the points where each bit changes from 0 to 1, then recursively searches either the 0's or 1's depending on which is more frequent. 63 | 64 | ## Day 4 65 | 66 | This solution creates a linked list of bingo boards and grid locations for each of the draw numbers. Marked boards are stored as 25 bits in an integer, and testing for a win is just two bit-mask and compare operations: one each for the marked row and column. 67 | 68 | ## Day 5 69 | 70 | After trying several different approaches, the one that seemed to work best was to scan the coordinate space row-by-row from top to bottom. Active line segments are tracked separately for the horizontal, vertical, diagonal, and anti-diagonal cases. For each of these cases, there is an array of counts (how many active line segments per vertical/diagonal/etc coordinate), a bitmap of which cells intersect at least one line, and a bitmap of which cells intersect multiple lines along the same vertical/diagonal/etc. 71 | 72 | Handling each line orientation separately simplifies advancing to the next row (bitwise shift left/right for the diagonal orientations.) Parts 1 and 2 can also be solved at the same time. 73 | 74 | ## Day 6 75 | 76 | The total lanternfish population satisfies the recurrence `f(n) = f(n - 7) + f(n - 9)`. After setting up initial conditions (populations for first 9 days), it just iterates out the remaining days. 77 | 78 | ## Day 7 79 | 80 | The optimal position for Part 1 is the median, and for Part 2 it is either the floor or ceiling of the mean. All computation is done using prefix-sum arrays (indexed by x-coordinate) for the first three powers of the x-coordinate (`x^0`, `x^1`, `x^2`). This avoids having to sort the list of crab submarines. 81 | 82 | ## Day 8 83 | 84 | Digit strings are converted to 7-bit numbers representing which segments are lit. The bitwise XOR and AND of the "easy" (Part 1) digit segments, together with the segment counts (5 or 6), uniquely identify the Part 2 digits. 85 | 86 | ## Day 9 87 | 88 | Both parts are solved in a single scan over the input. Contiguous intervals of basin locations (digits 0-8) are joined with neighboring intervals from the previous row using the union-find algorithm. Basin size and low point are propagated up to the disjoint set root nodes. 89 | 90 | ## Day 10 91 | 92 | Relatively straightforward solution to the parenthesis matching problem. Uses `std::nth_element` to find the middle score without fully sorting the list. 93 | 94 | ## Day 11 95 | 96 | ## Day 12 97 | 98 | ## Day 13 99 | 100 | ## Day 14 101 | 102 | ## Day 15 103 | 104 | ## Day 16 105 | 106 | ## Day 17 107 | 108 | ## Day 18 109 | 110 | ## Day 19 111 | 112 | ## Day 20 113 | 114 | ## Day 21 115 | 116 | ## Day 22 117 | 118 | ## Day 23 119 | 120 | ## Day 24 121 | 122 | ## Day 25 123 | -------------------------------------------------------------------------------- /input/README.md: -------------------------------------------------------------------------------- 1 | Copy your inputs here and name them `day01.txt`, `day02.txt`, etc. 2 | 3 | Input files should be unmodified copies of the originals (use "Save As" 4 | when downloding from the Advent of Code website.) 5 | -------------------------------------------------------------------------------- /src/advent2021.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2021_H 2 | #define _ADVENT2021_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | struct input_t { 20 | char *s; 21 | ssize_t len; 22 | }; 23 | 24 | struct output_t { 25 | std::array answer; 26 | 27 | template 28 | output_t(T1 p1, T2 p2) { 29 | set(0, p1); 30 | set(1, p2); 31 | } 32 | 33 | template 34 | void set(int part, T value) { 35 | std::stringstream ss; 36 | ss << value; 37 | answer[part] = ss.str(); 38 | } 39 | }; 40 | 41 | struct advent_t { 42 | output_t (*fn)(input_t); 43 | }; 44 | 45 | output_t day01(input_t); 46 | output_t day02(input_t); 47 | output_t day03(input_t); 48 | output_t day04(input_t); 49 | output_t day05(input_t); 50 | output_t day06(input_t); 51 | output_t day07(input_t); 52 | output_t day08(input_t); 53 | output_t day09(input_t); 54 | output_t day10(input_t); 55 | output_t day11(input_t); 56 | output_t day12(input_t); 57 | output_t day13(input_t); 58 | output_t day14(input_t); 59 | output_t day15(input_t); 60 | output_t day16(input_t); 61 | output_t day17(input_t); 62 | output_t day18(input_t); 63 | output_t day19(input_t); 64 | output_t day20(input_t); 65 | output_t day21(input_t); 66 | output_t day22(input_t); 67 | output_t day23(input_t); 68 | output_t day24(input_t); 69 | output_t day25(input_t); 70 | 71 | #include "parse.h" 72 | #include "bits.h" 73 | #include "numeric.h" 74 | #include "ocr.h" 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/bits.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2021_BITS 2 | #define _ADVENT2021_BITS 3 | 4 | // Iterate over bits using range-for syntax: 5 | // for (int i : bits(mask)) 6 | template 7 | struct bits { 8 | T mask; 9 | bits(T mask) : mask(mask) { } 10 | bits& operator++ () { mask &= mask - 1; return *this; } 11 | bool operator!= (const bits &o) const { return mask != o.mask; } 12 | inline int operator* () const { return __builtin_ctzll(mask); } 13 | bits begin() const { return mask; } 14 | bits end() const { return 0; } 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/day01.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day01(input_t in) { 4 | int part1 = 0, part2 = 0; 5 | 6 | // Use a 64-bit integer as a FIFO of 16-bit integers 7 | uint64_t previous = INT64_MAX; 8 | uint16_t n; 9 | while ((n = parse::positive(in))) { 10 | part1 += n > uint16_t(previous); 11 | part2 += n > uint16_t(previous >> 32); 12 | previous = (previous << 16) + n; 13 | } 14 | 15 | return { part1, part2 }; 16 | } 17 | -------------------------------------------------------------------------------- /src/day02.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day02(input_t in) { 4 | enum { CMD_DOWN, CMD_UP, CMD_FORWARD, CMD_UNKNOWN }; 5 | 6 | constexpr int8_t SKIP[] = { 7 | 5, // "down " 8 | 3, // "up " 9 | 8, // "forward " 10 | 0 11 | }; 12 | 13 | int x = 0, aim = 0, depth = 0; 14 | 15 | while (in.len > 0) { 16 | int command = *in.s & 3; 17 | parse::skip(in, SKIP[command] + 2); 18 | int n = *(in.s - 2) - '0'; 19 | 20 | /* Branchless conditionals. They don't really help here, 21 | * but it's a simple example in case you encounter it 22 | * later in this solution set. 23 | * 24 | * The comparison evaluates to 0 or 1, and negating that 25 | * gives 00000... or 11111... in binary, which we can 26 | * use to mask the value being added or subtracted. 27 | */ 28 | aim += n & -(command == CMD_DOWN ); 29 | aim -= n & -(command == CMD_UP ); 30 | x += n & -(command == CMD_FORWARD); 31 | depth += (n * aim) & -(command == CMD_FORWARD); 32 | } 33 | 34 | int part1 = x * aim; 35 | int part2 = x * depth; 36 | 37 | return { part1, part2 }; 38 | } 39 | -------------------------------------------------------------------------------- /src/day03.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | /* Part 1 can also be done using 16 x 8-bit SIMD; it works 4 | * out nicely because the input characters can be loaded 5 | * directly into the register. The "parse" is simply a 6 | * bitwise-and with 1. Total runtime for a SIMD Part 1 7 | * implementation is about a microsecond. However with 8 | * more and more ARM chips out there, we're going to 9 | * eschew the SIMD for portability. 10 | */ 11 | 12 | output_t day03(input_t in) { 13 | if (in.len != 13000) abort(); 14 | 15 | std::array exists = { }; 16 | std::array counts = { }; 17 | 18 | for (char *p = in.s, *end = in.s + in.len; p != end; p += 13) { 19 | int value = 0; 20 | for (int i = 0; i < 12; i++) { 21 | counts[i] += (p[i] == '1'); 22 | value |= (p[i] == '1') << (11 - i); 23 | } 24 | exists[value / 64] |= uint64_t(1) << (value % 64); 25 | } 26 | 27 | // Build a sorted list of values from the bit set 28 | std::array values; 29 | for (int i = 0, idx = 0; i < 64; i++) { 30 | for (auto j : bits(exists[i])) { 31 | values[idx++] = 64 * i + j; 32 | } 33 | } 34 | 35 | // Binary search for partition point for bit position 36 | auto partition = [values](int begin, int end, uint16_t bit) { 37 | for (int size = end - begin; size; ) { 38 | int half = size / 2, mid = begin + half; 39 | if (~values[mid] & bit) { 40 | begin = mid + 1; 41 | size -= half + 1; 42 | } else { 43 | size = half; 44 | } 45 | } 46 | return begin; 47 | }; 48 | 49 | // Lookup carbon or oxygen value, depending on argument 50 | auto lookup = [partition](int begin, int end, int mid, bool oxygen) { 51 | for (int bit = 0x400; begin + 1 != end; bit >>= 1) { 52 | if ((end - mid < mid - begin) ^ oxygen) { 53 | begin = mid; 54 | } else { 55 | end = mid; 56 | } 57 | mid = partition(begin, end, bit); 58 | } 59 | return begin; 60 | }; 61 | 62 | // Part 1 63 | int gamma = 0; 64 | for (int i = 0; i < 12; i++) { 65 | gamma |= (counts[i] > 500) << (11 - i); 66 | } 67 | int epsilon = gamma ^ 0xfff; 68 | int part1 = gamma * epsilon; 69 | 70 | // Part 2 71 | int mid = partition(0, 1000, 0x800); 72 | int oxygen = values[lookup(0, 1000, mid, true )]; 73 | int carbon = values[lookup(0, 1000, mid, false)]; 74 | int part2 = oxygen * carbon; 75 | 76 | return { part1, part2 }; 77 | } 78 | -------------------------------------------------------------------------------- /src/day04.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day04(input_t in) { 4 | constexpr int INPUT_LEN = 7890; 5 | constexpr int NUM_BOARDS = 100; 6 | constexpr int NUM_NUMBERS = 100; 7 | constexpr int BOARDS_LEN = 76 * NUM_BOARDS; 8 | 9 | if (in.len != INPUT_LEN) abort(); 10 | 11 | struct Entry { 12 | int8_t b, pos; 13 | int16_t next; 14 | }; 15 | std::vector lookup(NUM_NUMBERS); 16 | lookup.reserve(NUM_NUMBERS + NUM_BOARDS * 25); 17 | 18 | std::vector board_sum(NUM_BOARDS); 19 | 20 | // Map each draw to the boards/positions where they occur 21 | char *p = in.s + (INPUT_LEN - BOARDS_LEN); 22 | char *end_numbers = p; 23 | 24 | for (int board = 0; board < NUM_BOARDS; board++) { 25 | p++; 26 | int sum = 0; 27 | for (int i = 0; i < 25; i++, p += 3) { 28 | int n = 10 * (p[0] & 0xf) + (p[1] & 0xf); 29 | if (n >= NUM_NUMBERS) abort(); 30 | if (lookup[n].next) { 31 | lookup.push_back(lookup[n]); 32 | lookup[n].next = lookup.size() - 1; 33 | } else { 34 | lookup[n].next = -1; 35 | } 36 | lookup[n].b = board; 37 | lookup[n].pos = i; 38 | sum += n; 39 | } 40 | board_sum[board] = sum; 41 | } 42 | 43 | // Did 'pos' just finish a row or column? 44 | auto is_solved = [](int b, int pos) { 45 | int column = pos % 5; 46 | int unfinished_row = 0x00001f & (~b >> (pos - column)); 47 | int unfinished_col = 0x108421 & (~b >> column); 48 | return !unfinished_row | !unfinished_col; 49 | }; 50 | 51 | int part1 = 0, part2 = 0; 52 | 53 | std::vector boards(NUM_BOARDS); 54 | p = in.s; 55 | for (int unsolved = NUM_BOARDS; unsolved && p < end_numbers; ) { 56 | int n = (*p++ - '0'); 57 | if (*p != ',') n = 10 * n + (*p++ - '0'); 58 | p++; 59 | if (n < 0 || n >= 100) abort(); 60 | 61 | if (!lookup[n].next) continue; 62 | 63 | for (int idx = n; idx != -1; idx = lookup[idx].next) { 64 | int b = lookup[idx].b; 65 | int pos = lookup[idx].pos; 66 | 67 | if (!board_sum[b]) continue; 68 | 69 | boards[b] |= 1 << pos; 70 | board_sum[b] -= n; 71 | 72 | if (is_solved(boards[b], pos)) { 73 | unsolved--; 74 | int score = n * board_sum[b]; 75 | board_sum[b] = 0; 76 | 77 | if (!part1) { 78 | part1 = score; 79 | } 80 | 81 | if (!unsolved) { 82 | part2 = score; 83 | break; 84 | } 85 | } 86 | } 87 | } 88 | 89 | return { part1, part2 }; 90 | } 91 | -------------------------------------------------------------------------------- /src/day05.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | struct Line { 6 | int16_t x0, x1, y0, y1; 7 | 8 | void normalize() { 9 | if (y0 > y1 || (y0 == y1 && x0 > x1)) { 10 | std::swap(x0, x1); 11 | std::swap(y0, y1); 12 | } 13 | } 14 | }; 15 | 16 | struct Event { 17 | int16_t idx, y; 18 | 19 | Event() { } 20 | 21 | Event(int16_t idx, int16_t y) : idx(idx), y(y) { } 22 | }; 23 | 24 | template 25 | auto histogram_sort(const std::vector &input, auto key) { 26 | std::array hist = { }; 27 | for (auto &v : input) { 28 | hist[key(v) + 1]++; 29 | } 30 | std::partial_sum(hist.begin(), hist.end(), hist.begin()); 31 | 32 | std::vector sorted(input.size()); 33 | for (auto &v : input) { 34 | sorted[hist[key(v)]++] = v; 35 | } 36 | 37 | return sorted; 38 | } 39 | 40 | template 41 | struct Bits { 42 | std::array B{}; 43 | 44 | Bits operator | (Bits o) const { 45 | for (int i = 0; i < Size; i++) { 46 | o.B[i] |= B[i]; 47 | } 48 | return o; 49 | } 50 | 51 | Bits operator & (Bits o) const { 52 | for (int i = 0; i < Size; i++) { 53 | o.B[i] &= B[i]; 54 | } 55 | return o; 56 | } 57 | 58 | void shift_up() { 59 | uint64_t carry = 0; 60 | for (int i = 0; i < Size; i++) { 61 | B[i] = std::exchange(carry, B[i] >> 63) | (B[i] << 1); 62 | } 63 | } 64 | 65 | void shift_down() { 66 | for (int i = 1; i < Size; i++) { 67 | B[i - 1] = (B[i - 1] >> 1) | (B[i] << 63); 68 | } 69 | B[Size - 1] >>= 1; 70 | } 71 | 72 | void flip(int i) { 73 | B[i >> 6] ^= uint64_t(1) << (i & 63); 74 | } 75 | 76 | void set_range(int lo, int hi) { 77 | int i0 = lo >> 6, i1 = hi >> 6; 78 | lo &= 63, hi &= 63; 79 | 80 | constexpr uint64_t ones(-1); 81 | if (i0 == i1) { 82 | B[i0] |= (ones << lo) & (ones >> (63 - hi)); 83 | } else { 84 | B[i0] |= ones << lo; 85 | for (int i = i0 + 1; i < i1; i++) { 86 | B[i] = ones; 87 | } 88 | B[i1] |= ones >> (63 - hi); 89 | } 90 | } 91 | 92 | int count() const { 93 | int n = 0; 94 | for (auto b : B) { 95 | n += __builtin_popcountll(b); 96 | } 97 | return n; 98 | } 99 | }; 100 | 101 | template 102 | struct RowBitmap { 103 | Bits any; // at least one line 104 | Bits dup; // multiple lines 105 | 106 | auto count() const { 107 | return dup.count(); 108 | } 109 | 110 | void merge_dup(RowBitmap &o) { 111 | dup = dup | o.dup | (any & o.any); 112 | } 113 | 114 | void merge(RowBitmap &o) { 115 | merge_dup(o); 116 | any = any | o.any; 117 | } 118 | 119 | void apply_horizontal_overlap(int a0, int a1, int b0, int b1) { 120 | a0 = std::max(a0, b0); 121 | a1 = std::min(a1, b1); 122 | 123 | any.set_range(b0, b1); 124 | if (a0 <= a1) { 125 | dup.set_range(a0, a1); 126 | } 127 | } 128 | 129 | void flip(int i, int count) { 130 | switch (count) { 131 | case 1: any.flip(i); break; 132 | case 2: dup.flip(i); break; 133 | } 134 | } 135 | 136 | void shift_up() { 137 | any.shift_up(); 138 | dup.shift_up(); 139 | } 140 | 141 | void shift_down() { 142 | any.shift_down(); 143 | dup.shift_down(); 144 | } 145 | }; 146 | 147 | } 148 | 149 | output_t day05(input_t in) { 150 | constexpr int DIM = 1000; 151 | 152 | std::vector lines; 153 | while (in.len > 0) { 154 | lines.emplace_back(); 155 | auto &l = lines.back(); 156 | l.x0 = parse::positive(in); parse::skip(in, 1); 157 | l.y0 = parse::positive(in); parse::skip(in, 4); 158 | l.x1 = parse::positive(in); parse::skip(in, 1); 159 | l.y1 = parse::positive(in); parse::skip(in, 1); 160 | l.normalize(); 161 | } 162 | 163 | // Ensure that horizontal lines are handled in order 164 | lines = histogram_sort(lines, 165 | [](const auto &l) { return l.x0; }); 166 | 167 | // Create an event for start and end of each line 168 | std::vector events; 169 | for (int i = 0; i < lines.size(); i++) { 170 | auto & [ x0, x1, y0, y1 ] = lines[i]; 171 | events.emplace_back(i, y0 * 2); 172 | events.emplace_back(i, y1 * 2 + 1); 173 | } 174 | 175 | // Sort events by increasing y-value 176 | events = histogram_sort(events, 177 | [](const Event &e) { return e.y; }); 178 | events.emplace_back(INT16_MAX, 0); 179 | 180 | int part1 = 0, part2 = 0; 181 | 182 | // Counts of active segments on each vertical, diagonal, antidiagonal 183 | std::array Vc{}, Dc{}, Ac{}; 184 | 185 | // Bitmaps of the current row 186 | RowBitmap<16> V, D, A; 187 | 188 | auto ei = events.begin(); 189 | for (int y = 0, n = 0; y < DIM; y++) { 190 | RowBitmap<16> H; 191 | 192 | // Shift the diagonals 193 | D.shift_up(); 194 | A.shift_down(); 195 | 196 | // Handle start-of-segment events 197 | int16_t h0 = -1, h1 = -1; 198 | for (; ei->y == n; ei++) { 199 | auto [ x0, x1, y0, y1 ] = lines[ei->idx]; 200 | if (x0 == x1) { 201 | V.flip(x0, ++Vc[x0]); 202 | } else if (y0 == y1) { 203 | H.apply_horizontal_overlap(h0, h1, x0, x1); 204 | h0 = x0, h1 = std::max(h1, x1); 205 | } else if (x0 < x1) { 206 | D.flip(x0, ++Dc[(x0 - y0) & 1023]); 207 | } else { 208 | A.flip(x0, ++Ac[(x0 + y0) & 1023]); 209 | } 210 | } 211 | n++; 212 | 213 | // Count the crossings for the current row 214 | H.merge(V); 215 | part1 += H.count(); 216 | H.merge(D); 217 | H.merge_dup(A); 218 | part2 += H.count(); 219 | 220 | // Handle end-of-segment events 221 | for (; ei->y == n; ei++) { 222 | auto [ x0, x1, y0, y1 ] = lines[ei->idx]; 223 | if (x0 == x1) { 224 | V.flip(x1, Vc[x1]--); 225 | } else if (y0 == y1) { 226 | // do nothing 227 | } else if (x0 < x1) { 228 | D.flip(x1, Dc[(x1 - y1) & 1023]--); 229 | } else { 230 | A.flip(x1, Ac[(x1 + y1) & 1023]--); 231 | } 232 | } 233 | n++; 234 | } 235 | 236 | return { part1, part2 }; 237 | } 238 | -------------------------------------------------------------------------------- /src/day06.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day06(input_t in) { 4 | constexpr int INITIAL = 9; 5 | 6 | std::array A; 7 | std::fill(A.begin(), A.begin() + INITIAL, 0); 8 | 9 | // Set up a recurrence for the lanternfish population 10 | for (; in.len > 0; parse::skip(in, 2)) { 11 | A[(*in.s + 1) & 7]++; 12 | } 13 | A[0] = A[2] + A[3] + A[4] + A[5] + A[6]; 14 | for (int i = 1; i < INITIAL; i++) { 15 | A[i] += A[i - 1]; 16 | } 17 | 18 | // Iterate out to day 256 19 | for (int i = INITIAL; i <= 256; i++) { 20 | A[i] = A[i - 7] + A[i - 9]; 21 | } 22 | 23 | uint64_t part1 = A[80], part2 = A[256]; 24 | 25 | return { part1, part2 }; 26 | } 27 | -------------------------------------------------------------------------------- /src/day07.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day07(input_t in) { 4 | std::array counts = { }; 5 | std::array sums = { }; 6 | uint32_t squares = 0; 7 | 8 | for (; in.len > 0; parse::skip(in, 1)) { 9 | int n = parse::positive(in) % 2048; 10 | counts[n]++; 11 | sums[n] += n; 12 | squares += n * n; 13 | } 14 | 15 | std::partial_sum(counts.begin(), counts.end(), counts.begin()); 16 | std::partial_sum(sums.begin(), sums.end(), sums.begin()); 17 | 18 | // The count is always 1000 anyway 19 | if (counts.back() % 2 != 0) abort(); 20 | 21 | auto midpoint = std::lower_bound( 22 | counts.begin(), counts.end(), counts.back() / 2); 23 | int median = std::distance(counts.begin(), midpoint); 24 | int part1 = (sums.back() - 2*sums[median]) 25 | + median*(2*counts[median] - counts.back()); 26 | 27 | auto try_part2 = [&](uint32_t mu) { 28 | int n = squares + sums.back() - 2*sums[mu] + 29 | mu*(2*(counts[mu] - sums.back()) 30 | + (mu-1)*counts.back()); 31 | return n / 2; 32 | }; 33 | 34 | /* Answer will round either up or down from the mean, but 35 | * it will not necessarily round toward the closest value 36 | */ 37 | int mean = sums.back() / counts.back(); 38 | int part2 = std::min(try_part2(mean), try_part2(mean + 1)); 39 | 40 | return { part1, part2 }; 41 | } 42 | -------------------------------------------------------------------------------- /src/day08.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day08(input_t in) { 4 | // Convert input letters to a bitmask and length 5 | auto parse_slug = [](char *&p) { 6 | int slug = 0; 7 | auto begin = p; 8 | for (; *p >= 'a'; p++) { 9 | slug |= 1 << (*p - 'a'); 10 | } 11 | return std::make_pair(slug, p - begin); 12 | }; 13 | 14 | // Easy lengths: 2, 3, 4, 7 15 | auto is_easy_length = [](int len) { 16 | return (0b10011100 >> len) & 1; 17 | }; 18 | 19 | /* Lookup by number of segments, returning either the 20 | * digit (for the easy lengths), or an offset for later 21 | * lookup in the by_magic() table. 22 | */ 23 | auto by_length = [](int len) { 24 | return (0x8fb47100 >> (len * 4)) & 0xf; 25 | }; 26 | 27 | /* Second-tier lookup table for digits with 5 or 6 segments. 28 | * The lookup index incorporates segment count, bitwise-and 29 | * of the easy digit segments, and their bitwise-xor. 30 | */ 31 | auto by_magic = [](int magic) { 32 | return (0x09600325 >> (magic * 4)) & 0xf; 33 | }; 34 | 35 | std::array digits; 36 | std::array slugs; 37 | 38 | int part1 = 0, part2 = 0; 39 | 40 | for (char *p = in.s, *end = p + in.len; p < end; ) { 41 | // First-pass scan over the digits 42 | uint8_t slugs_and = 0xff, slugs_xor = 0; 43 | for (int i = 0; i < 10; i++, p++) { 44 | auto [ slug, len ] = parse_slug(p); 45 | slugs[i] = slug; 46 | digits[slug] = by_length(len); 47 | if (is_easy_length(len)) { 48 | slugs_and &= slug; 49 | slugs_xor ^= slug; 50 | } 51 | } 52 | p += 2; 53 | 54 | // Finish mapping the digits 55 | for (int i = 0; i < 10; i++) { 56 | int slug = slugs[i], magic = digits[slug]; 57 | if (magic > 10) { 58 | magic &= 4; 59 | magic |= !(~slug & slugs_and) << 1; 60 | magic |= !(~slug & slugs_xor); 61 | digits[slug] = by_magic(magic); 62 | } 63 | } 64 | 65 | // Solve part1 + part2 66 | int number = 0; 67 | for (int i = 0; i < 4; i++, p++) { 68 | auto [ slug, len ] = parse_slug(p); 69 | part1 += is_easy_length(len); 70 | number = 10 * number + digits[slug]; 71 | } 72 | part2 += number; 73 | } 74 | 75 | return { part1, part2 }; 76 | } 77 | -------------------------------------------------------------------------------- /src/day09.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | /* Performs a line sweep over the map, identifying contiguous intervals 4 | * of basin (0-8) locations. These intervals are joined with the previous 5 | * row using a disjoint set data structure which tracks the size and low 6 | * point of each basin. 7 | */ 8 | 9 | namespace { 10 | 11 | // Open interval [lo, hi) 12 | struct interval { 13 | interval *parent; 14 | uint8_t lo, hi; 15 | uint8_t min, size; 16 | 17 | interval() { 18 | } 19 | 20 | interval(uint8_t lo, uint8_t hi, uint8_t min) : 21 | lo(lo), hi(hi), 22 | min(min), size(hi - lo) 23 | { 24 | } 25 | 26 | interval * find() { 27 | interval *root = this; 28 | while (root != root->parent) { 29 | root = root->parent = root->parent->parent; 30 | } 31 | return root; 32 | } 33 | 34 | void join(interval *other) { 35 | auto a = find(), b = other->find(); 36 | if (a == b) return; 37 | if (a->size < b->size) std::swap(a, b); 38 | b->parent = a; 39 | a->size += b->size; 40 | a->min = std::min(a->min, b->min); 41 | } 42 | }; 43 | 44 | // Allocation pool 45 | struct interval_pool { 46 | std::vector pool; 47 | int size; 48 | 49 | interval_pool(int size) : pool(size), size(size) { 50 | } 51 | 52 | auto alloc(uint8_t lo, uint8_t hi, uint8_t min) { 53 | auto ret = &pool[--size]; 54 | *ret = interval{lo, hi, min}; 55 | ret->parent = ret; 56 | return ret; 57 | } 58 | 59 | auto begin() { 60 | return pool.begin() + size; 61 | } 62 | 63 | auto end() { 64 | return pool.end(); 65 | } 66 | }; 67 | 68 | } 69 | 70 | output_t day09(input_t in) { 71 | constexpr int DIM = 100; 72 | 73 | if (in.len != DIM * (DIM + 1)) abort(); 74 | 75 | interval SENTINEL{UINT8_MAX, UINT8_MAX, 0}; 76 | interval_pool pool(DIM * DIM + 1); 77 | 78 | std::vector prev = { &SENTINEL }, curr; 79 | for (int r = 0; r < DIM; r++, in.s++) { 80 | // Convert this row into basin intervals 81 | uint8_t lo = 0, min = '9'; 82 | for (int c = 0; c < DIM; c++, in.s++) { 83 | if (*in.s == '9') { 84 | if (lo != c) { 85 | curr.push_back(pool.alloc(lo, c, min)); 86 | } 87 | lo = c + 1, min = '9'; 88 | } else { 89 | min = std::min(min, uint8_t(*in.s)); 90 | } 91 | } 92 | if (lo != DIM) { 93 | curr.emplace_back(pool.alloc(lo, DIM, min)); 94 | } 95 | 96 | // Join this row's intervals with the last row 97 | auto p = prev.begin(); 98 | for (auto r : curr) { 99 | while ((*p)->hi <= r->lo) p++; 100 | while ((*p)->hi <= r->hi) r->join(*p++); 101 | if ((*p)->lo < r->hi) r->join(*p); 102 | } 103 | 104 | curr.push_back(&SENTINEL); 105 | prev.swap(curr); 106 | curr.clear(); 107 | } 108 | 109 | int part1 = 0, part2 = 0; 110 | 111 | // Find all disjoint-set roots and collect their 112 | // sizes and minimum values 113 | std::vector sizes; 114 | sizes.reserve(std::distance(pool.begin(), pool.end())); 115 | for (auto &r : pool) { 116 | if (r.find() == &r) { 117 | part1 += r.min - '0' + 1; 118 | sizes.push_back(r.size); 119 | } 120 | } 121 | 122 | // Take the product of the top three sizes 123 | std::partial_sort( 124 | sizes.begin(), sizes.begin() + 3, sizes.end(), 125 | std::greater{}); 126 | part2 = sizes[0] * sizes[1] * sizes[2]; 127 | 128 | return { part1, part2 }; 129 | } 130 | -------------------------------------------------------------------------------- /src/day10.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day10(input_t in) { 4 | auto error_score = [](char c) { 5 | switch (c) { 6 | case ')': return 3; 7 | case ']': return 57; 8 | case '}': return 1197; 9 | case '>': return 25137; 10 | default: return 0; 11 | } 12 | }; 13 | 14 | auto autocomplete_score = [](auto &stack) { 15 | int64_t score = 0; 16 | while (stack.size() > 1) { 17 | int c = stack.back(); 18 | score = score * 5 + 1 + (0x10980 >> (c >> 3)) % 4; 19 | stack.pop_back(); 20 | } 21 | return score; 22 | }; 23 | 24 | std::vector stack = { 0 }; 25 | std::vector score; 26 | 27 | int64_t part1 = 0; 28 | for (auto p = in.s, end = p + in.len; p != end; p++) { 29 | if (*p == '\n') { 30 | score.push_back(autocomplete_score(stack)); 31 | } else if ((0x19 >> (*p & 7)) & 1) { 32 | stack.push_back(*p); 33 | } else if (stack.back() + 2 == *p + (*p == ')')) { 34 | stack.pop_back(); 35 | } else { 36 | part1 += error_score(*p); 37 | p = strchr(p, '\n'); 38 | stack.resize(1); 39 | } 40 | } 41 | 42 | auto midpoint = score.size() / 2; 43 | std::nth_element(score.begin(), score.begin() + midpoint, score.end()); 44 | int64_t part2 = score[midpoint]; 45 | 46 | return { part1, part2 }; 47 | } 48 | -------------------------------------------------------------------------------- /src/day11.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | struct Dumbo { 6 | std::array D; 7 | 8 | Dumbo() { } 9 | 10 | Dumbo(uint64_t row) { 11 | std::fill(D.begin(), D.end(), row); 12 | } 13 | 14 | // Return row with all 10 positions set to n 15 | static constexpr uint64_t SET1(uint64_t n) { 16 | return (1LLU << 60) / 63 * n; 17 | } 18 | 19 | // Return true if all positions are zero 20 | bool empty() const { 21 | for (auto row : D) { 22 | if (row) return false; 23 | } 24 | return true; 25 | } 26 | 27 | // Count ones (expects only 0 or 1 in all positions) 28 | int count() const { 29 | int sum = 0; 30 | for (auto row : D) { 31 | sum += __builtin_popcountll(row); 32 | } 33 | return sum; 34 | } 35 | 36 | // Bitwise-or 37 | Dumbo operator | (const Dumbo &o) const { 38 | Dumbo N; 39 | for (int i = 0; i < D.size(); i++) { 40 | N.D[i] = D[i] | o.D[i]; 41 | } 42 | return N; 43 | } 44 | 45 | // Add a constant to all positions 46 | Dumbo operator + (uint64_t n) const { 47 | Dumbo N; 48 | for (int i = 0; i < D.size(); i++) { 49 | N.D[i] = D[i] + SET1(n); 50 | } 51 | return N; 52 | } 53 | 54 | // Return array of ones in positions that are 10 or more 55 | Dumbo tens() const { 56 | Dumbo N; 57 | for (int i = 0; i < D.size(); i++) { 58 | N.D[i] = ((D[i] + SET1(22)) >> 5) & SET1(1); 59 | } 60 | return N; 61 | } 62 | 63 | // Spread out values 3-wide 64 | Dumbo spread() const { 65 | Dumbo N; 66 | for (int i = 0; i < D.size(); i++) { 67 | N.D[i] = (D[i] + (D[i] << 6) + (D[i] >> 6)) & SET1(63); 68 | } 69 | return N; 70 | } 71 | 72 | // Spread values in addend 3-tall and add to this 73 | Dumbo add_y3(const Dumbo &o) const { 74 | Dumbo N; 75 | N.D[0] = D[0] + o.D[0]; 76 | for (int i = 1; i < D.size(); i++) { 77 | N.D[i - 1] += o.D[i]; 78 | N.D[i] = D[i] + o.D[i] + o.D[i - 1]; 79 | } 80 | return N; 81 | } 82 | 83 | // Zero out positions identified by ones in the mask 84 | Dumbo zero(const Dumbo &mask) const { 85 | Dumbo N; 86 | for (int i = 0; i < D.size(); i++) { 87 | N.D[i] = D[i] & ~(SET1(32) - mask.D[i]); 88 | } 89 | return N; 90 | } 91 | }; 92 | 93 | }; 94 | 95 | output_t day11(input_t in) { 96 | int part1 = 0, part2 = 0; 97 | 98 | if (in.len != 110) abort(); 99 | 100 | Dumbo dumbo; 101 | 102 | for (auto &r : dumbo.D) { 103 | r = 0; 104 | for (int j = 0; j < 10; j++) { 105 | r = (r << 6) | (in.s[j] - '0'); 106 | } 107 | parse::skip(in, 11); 108 | } 109 | 110 | for (int i = 1; ; i++) { 111 | dumbo = dumbo + 1; 112 | 113 | auto tens = dumbo.tens(); 114 | if (tens.empty()) continue; 115 | 116 | auto flashed = tens; 117 | for (;;) { 118 | dumbo = dumbo.add_y3(tens.spread()).zero(flashed); 119 | tens = dumbo.tens(); 120 | if (tens.empty()) break; 121 | flashed = flashed | tens; 122 | } 123 | 124 | if (i <= 100) { 125 | part1 += flashed.count(); 126 | } 127 | 128 | if (dumbo.empty()) { 129 | part2 = i; 130 | break; 131 | } 132 | } 133 | 134 | return { part1, part2 }; 135 | } 136 | -------------------------------------------------------------------------------- /src/day12.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | constexpr uint64_t set1(uint64_t n) { 6 | return uint64_t(-1) / 15 * n; 7 | } 8 | 9 | } 10 | 11 | output_t day12(input_t in) { 12 | std::unordered_map name; 13 | 14 | constexpr int END = 8, START = 9; 15 | 16 | name[0x656E64] = END; 17 | name[0x74617274] = START; 18 | 19 | std::array edge{}; 20 | std::vector big; 21 | 22 | uint32_t word = 0; 23 | int n_small = 0; 24 | int id0 = 0; 25 | bool is_big0 = false; 26 | for (auto end = in.s + in.len; in.s < end; in.s++) { 27 | if (*in.s >= 'A') { 28 | word = (word << 8) | *in.s; 29 | } else { 30 | bool is_big = !(word & 0x20); 31 | int next_id = is_big ? 0x80 + big.size() : n_small; 32 | 33 | auto [ it, ok ] = name.emplace(word, next_id); 34 | int id = it->second; 35 | 36 | if (ok) { 37 | if (is_big) { 38 | big.push_back(0); 39 | } else { 40 | n_small++; 41 | } 42 | } 43 | 44 | if (*in.s == '-') { 45 | id0 = id; 46 | is_big0 = is_big; 47 | } else if (is_big0) { 48 | big[id0 ^ 0x80] |= 1LL << (4 * id); 49 | } else if (is_big) { 50 | big[id ^ 0x80] |= 1LL << (4 * id0); 51 | } else { 52 | edge[id] += 1LL << (4 * id0); 53 | edge[id0] += 1LL << (4 * id); 54 | } 55 | 56 | word = 0; 57 | } 58 | } 59 | 60 | for (auto b : big) { 61 | for (auto s : bits(b)) { 62 | edge[s / 4] += b; 63 | } 64 | } 65 | 66 | // remove edges leading to start 67 | for (auto &e : edge) { 68 | e &= 0xfffffffff; 69 | } 70 | 71 | int part1 = 0, part2 = 0; 72 | 73 | // TODO cleanup 74 | { 75 | // Part 1+2 prototype 76 | // key: (visited << 3) | cur 77 | // visited & (1 << n_small) if any small cave twice visited 78 | // note: visited excludes start,end 79 | std::vector P2(4096); 80 | 81 | // Expand the start node 82 | for (auto b : bits((edge[START] + set1(7)) & set1(8))) { 83 | int i = b / 4; 84 | b &= -4; 85 | int weight = (edge[START] >> b) & 7; 86 | if (i == END) { 87 | part1 += weight; 88 | } else { 89 | P2[(8 << i) | i] += weight; 90 | } 91 | } 92 | std::vector cur, next; 93 | for (int b = 1 << (n_small - 1); b > 0; b >>= 1) { 94 | next.push_back(b); 95 | } 96 | 97 | cur.swap(next); 98 | while (!cur.empty()) { 99 | for (auto n : cur) { 100 | for (int b = 1 << n_small; b > n; b >>= 1) { 101 | next.push_back(n | b); 102 | } 103 | int base = n << 3; 104 | for (auto s : bits(n & 0xff)) { 105 | int cur_weight = P2[base | s]; 106 | if (!cur_weight) continue; 107 | for (auto b : bits((edge[s] + set1(7)) & set1(8))) { 108 | int i = b / 4; 109 | b &= -4; 110 | int weight = (edge[s] >> b) & 7; 111 | if (i == END) { 112 | if (~n & (1 << n_small)) { 113 | part1 += weight * cur_weight; 114 | } else { 115 | part2 += weight * cur_weight; 116 | } 117 | } else { 118 | int new_idx = base | (8 << i) | i; 119 | if (n & (1 << i)) { 120 | if (n & (1 << n_small)) continue; 121 | new_idx |= (8 << n_small); 122 | } 123 | P2[new_idx] += weight * cur_weight; 124 | } 125 | } 126 | } 127 | } 128 | cur.swap(next); 129 | next.clear(); 130 | } 131 | } 132 | 133 | part2 += part1; 134 | 135 | return { part1, part2 }; 136 | } 137 | -------------------------------------------------------------------------------- /src/day13.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day13(input_t in) { 4 | constexpr int FOLD_X1 = 655, MAX_Y = 895; 5 | constexpr int FOLD_X2 = 40, FOLD_Y2 = 6; 6 | 7 | auto fold = [](int w, int n) { 8 | int div = n / (w + 1), mod = n % (w + 1); 9 | return (div & 1) ? (w - 1) - mod : mod; 10 | }; 11 | 12 | std::bitset P1; 13 | std::bitset P2; 14 | while (*in.s > '\n') { 15 | auto x = parse::positive(in); 16 | auto y = parse::positive(in); 17 | parse::skip(in, 1); 18 | if (y > MAX_Y) abort(); 19 | P1.set(fold(FOLD_X1, x) + y * FOLD_X1); 20 | P2.set(fold(FOLD_X2, x) + fold(FOLD_Y2, y) * FOLD_X2); 21 | } 22 | 23 | int part1 = P1.count(); 24 | 25 | std::string part2(8, ' '); 26 | for (int idx = 0, x0 = 0; x0 < FOLD_X2; idx++, x0 += OCR_WIDTH) { 27 | part2[idx] = ocr(&P2, 28 | [x0](auto *p, int y, int x) { 29 | return p->test(FOLD_X2 * y + x0 + x); 30 | }); 31 | } 32 | 33 | return { part1, part2 }; 34 | } 35 | -------------------------------------------------------------------------------- /src/day14.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day14(input_t in) { 4 | auto map = [](char ch) { 5 | constexpr std::array M = { 6 | 0, 0, 0, 1, 2, 3, 4, 5, 6, 0, 7, 8, 9, 0, 0 7 | }; 8 | return M[ch % 15]; 9 | }; 10 | 11 | if (in.len != 822) abort(); 12 | 13 | std::vector count(100), next(100); 14 | std::array, 100> trans{}; 15 | 16 | uint8_t first = map(*in.s++), last; 17 | for (uint8_t i = 1, prev = first; i < 20; i++) { 18 | last = map(*in.s++); 19 | count[prev * 10 + last]++; 20 | prev = last; 21 | } 22 | 23 | in.s += 2; 24 | for (uint8_t i = 0; i < 100; i++) { 25 | uint8_t s0 = map(in.s[0]); 26 | uint8_t s1 = map(in.s[1]); 27 | uint8_t d1 = map(in.s[6]); 28 | trans[s0 * 10 + s1][0] = s0 * 10 + d1; 29 | trans[s0 * 10 + s1][1] = d1 * 10 + s1; 30 | in.s += 8; 31 | } 32 | 33 | auto step = [&count, &next, trans]() { 34 | for (int i = 0; i < 100; i++) { 35 | next[trans[i][0]] += count[i]; 36 | next[trans[i][1]] += count[i]; 37 | } 38 | count.swap(next); 39 | std::fill(next.begin(), next.end(), 0); 40 | }; 41 | 42 | auto score = [&count, first, last]() { 43 | std::array sum{}; 44 | sum[first]++; 45 | sum[last]++; 46 | for (int i = 0, s0 = 0; s0 < 10; s0++) { 47 | for (int s1 = 0; s1 < 10; s1++, i++) { 48 | sum[s0] += count[i]; 49 | sum[s1] += count[i]; 50 | } 51 | } 52 | auto [ lo, hi ] = std::minmax_element(sum.begin(), sum.end()); 53 | return (*hi - *lo) / 2; 54 | }; 55 | 56 | for (int i = 0; i < 10; i++) step(); 57 | auto part1 = score(); 58 | for (int i = 10; i < 40; i++) step(); 59 | auto part2 = score(); 60 | 61 | return { part1, part2 }; 62 | } 63 | -------------------------------------------------------------------------------- /src/day15.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day15(input_t in) { 4 | constexpr int DIM = 512, INPUT_DIM = 100, INPUT_BIG = 500; 5 | 6 | if (in.len != (INPUT_DIM + 1) * INPUT_DIM) abort(); 7 | 8 | std::vector lookup(DIM * DIM); 9 | 10 | for (int r = 1; r <= INPUT_DIM; r++) { 11 | auto row = &lookup[DIM*r + 1]; 12 | for (int c = 0; c < INPUT_DIM; c++) { 13 | row[c] = in.s[c] - '0'; 14 | } 15 | in.s += INPUT_DIM + 1; 16 | row += DIM; 17 | } 18 | 19 | auto search = [&lookup](int r, int c) { 20 | int goal = DIM * r + c; 21 | 22 | std::array, 17> Q{}; 23 | Q[0].emplace_back(DIM*1 + 1); 24 | 25 | for (int qi = 0; ; qi++) { 26 | Q[16].clear(); 27 | Q[qi % 16].swap(Q[16]); 28 | 29 | for (auto p : Q[16]) { 30 | if (lookup[p] < 1) continue; 31 | if (p == goal) return qi; 32 | lookup[p] |= 0x80; 33 | for (int delta : { -1, 1, -DIM, DIM }) { 34 | auto n = p + delta; 35 | if (lookup[n] < 1) continue; 36 | Q[(qi + lookup[n]) % 16].push_back(n); 37 | } 38 | } 39 | } 40 | }; 41 | 42 | int part1 = search(INPUT_DIM, INPUT_DIM); 43 | 44 | for (int r = 1; r <= INPUT_DIM; r++) { 45 | for (int x = 1; x <= INPUT_DIM; x++) { 46 | lookup[DIM*r + x] &= 0x7f; 47 | } 48 | } 49 | 50 | for (int r = 1; r <= INPUT_DIM; r++) { 51 | auto cur = &lookup[DIM*r + INPUT_DIM + 1], prev = cur - INPUT_DIM; 52 | for (int c = INPUT_DIM + 1; c <= INPUT_BIG; c++) { 53 | auto risk = *prev++; 54 | *cur++ = (risk & -(risk < 9)) + 1; 55 | } 56 | } 57 | for (int r = INPUT_DIM + 1; r <= INPUT_BIG; r++) { 58 | auto cur = &lookup[DIM*r + 1], prev = cur - DIM * INPUT_DIM; 59 | for (int c = 1; c <= INPUT_BIG; c++) { 60 | auto risk = *prev++; 61 | *cur++ = (risk & -(risk < 9)) + 1; 62 | } 63 | } 64 | for (int r = 0; r < DIM; r++) { 65 | lookup[DIM*r + 0] = 0; 66 | lookup[DIM*r + INPUT_BIG + 1] = 0; 67 | } 68 | 69 | int part2 = search(INPUT_BIG, INPUT_BIG); 70 | 71 | return { part1, part2 }; 72 | } 73 | -------------------------------------------------------------------------------- /src/day16.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | struct bitstream { 6 | std::vector B; 7 | int idx = 0, cur = 64, len = 0; 8 | 9 | void push_hex(uint64_t nibble) { 10 | if (len % 64 == 0) B.push_back(0); 11 | B.back() |= nibble << (60 - len % 64); 12 | len += 4; 13 | } 14 | 15 | uint64_t take(int n) { 16 | uint64_t value; 17 | if (n <= cur) { 18 | cur -= n; 19 | value = B[idx] >> cur; 20 | } else { 21 | value = B[idx++] << (n - cur); 22 | cur = 64 - n + cur; 23 | value |= B[idx] >> cur; 24 | } 25 | len -= n; 26 | return value & ((1LL << n) - 1); 27 | } 28 | 29 | uint64_t take_varint() { 30 | uint64_t frag = take(5), value = 0; 31 | while (frag & 0x10) { 32 | value |= frag ^ 0x10; 33 | value <<= 4; 34 | frag = take(5); 35 | } 36 | return value | frag; 37 | } 38 | 39 | auto take_version_type() { 40 | int n = take(6); 41 | return std::make_pair((n >> 3) & 7, n & 7); 42 | } 43 | }; 44 | 45 | std::pair solve(bitstream &B) { 46 | auto [ version, type ] = B.take_version_type(); 47 | 48 | if (type == 4) { 49 | return { version, B.take_varint() }; 50 | } 51 | 52 | int packets = INT32_MAX, end = 0; 53 | 54 | if (B.take(1) == 0) { 55 | int length = B.take(15); 56 | end = B.len - length; 57 | } else { 58 | packets = B.take(11); 59 | } 60 | 61 | auto [ sum, value ] = solve(B); 62 | sum += version; 63 | 64 | while (--packets && B.len > end) { 65 | auto [ child_sum, child_value ] = solve(B); 66 | sum += child_sum; 67 | switch (type) { 68 | case 0: value += child_value; break; 69 | case 1: value *= child_value; break; 70 | case 2: value = std::min(value, child_value); break; 71 | case 3: value = std::max(value, child_value); break; 72 | case 5: value = (value > child_value); break; 73 | case 6: value = (value < child_value); break; 74 | case 7: value = (value == child_value); break; 75 | } 76 | } 77 | 78 | return { sum, value }; 79 | } 80 | 81 | } 82 | 83 | output_t day16(input_t in) { 84 | bitstream B; 85 | in.len--; 86 | for (int i = 0; i < in.len; i++) { 87 | in.s[i] -= '0' + ((in.s[i] >> 4) ^ 3); 88 | } 89 | for (int i = 0; i < in.len; i++) { 90 | B.push_hex(in.s[i]); 91 | } 92 | 93 | auto [ part1, part2 ] = solve(B); 94 | 95 | return { part1, part2 }; 96 | } 97 | -------------------------------------------------------------------------------- /src/day17.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | // Closed interval 6 | struct range { 7 | int lo, hi; 8 | 9 | range() : lo(), hi() { 10 | } 11 | 12 | range(int lo, int hi) : lo(lo), hi(hi) { 13 | } 14 | 15 | range(input_t &in) { 16 | lo = parse::integer(in); 17 | hi = parse::integer(in); 18 | } 19 | 20 | int size() const { 21 | return (lo <= hi) ? (hi - lo + 1) : 0; 22 | } 23 | 24 | range intersect(const range &o) const { 25 | return range{std::max(lo, o.lo), std::min(hi, o.hi)}; 26 | } 27 | 28 | range min(const range &o) const { 29 | return range{std::min(lo, o.lo), std::min(hi, o.hi)}; 30 | } 31 | }; 32 | 33 | static int floor_div(int n, int d) { 34 | int q = n / d, r = n % d; 35 | return q - (r && ((n < 0) ^ (d < 0))); 36 | } 37 | 38 | static int ceil_div(int n, int d) { 39 | int q = n / d, r = n % d; 40 | return q + (r && !((n < 0) ^ (d < 0))); 41 | } 42 | 43 | /* Get the vy-bounds to hit (L <= y <= H) 44 | * 2L/n + n - 1 <= 2v <= 2H/n + n - 1 45 | */ 46 | static int velocity_bound(int L, int n, auto div_fn) { 47 | /* This can be simplified to a single division, but 48 | * two divisions clarifies how hyper_count works 49 | */ 50 | return div_fn(div_fn(2*L, n) + n - 1, 2); 51 | } 52 | 53 | static int hyper_count(range ry, int min_velocity) { 54 | int count = 0; 55 | 56 | range vy_bound{min_velocity, -ry.lo}; 57 | 58 | for (int k = 1; k <= -ry.hi && vy_bound.size() > 0; k++) { 59 | range rn{ceil_div(-ry.hi*2, k), floor_div(-ry.lo*2, k)}; 60 | 61 | // Adjustments to account for velocity_bound rounding 62 | rn.lo += (rn.lo ^ ~k) & 1; 63 | rn.hi -= (rn.hi ^ ~k) & 1; 64 | 65 | if (rn.size() == 0) continue; 66 | 67 | range rvy{ 68 | velocity_bound(ry.lo, rn.lo, ceil_div), 69 | velocity_bound(ry.hi, rn.hi, floor_div)}; 70 | 71 | rvy = rvy.intersect(vy_bound); 72 | count += rvy.size(); 73 | vy_bound.hi = std::min(vy_bound.hi, rvy.lo - 1); 74 | } 75 | 76 | return count; 77 | } 78 | 79 | static int day17_part2(range rx, range ry) { 80 | /* Get the vx-bounds to hit (L <= x <= H) 81 | * 2L/n + n - 1 <= 2v <= 2H/n + n - 1 82 | * Except that vx stops at zero. 83 | * We should be able to detect this by taking the 84 | * minimum boundary value seen so far. 85 | */ 86 | int answer = 0; 87 | range prev_rvx{INT32_MAX, INT32_MAX}, prev_rvy; 88 | 89 | int n = 1; 90 | for (bool done = false; !done; n++) { 91 | range rvx{ 92 | velocity_bound(rx.lo, n, ceil_div), 93 | velocity_bound(rx.hi, n, floor_div)}; 94 | range rvy{ 95 | velocity_bound(ry.lo, n, ceil_div), 96 | velocity_bound(ry.hi, n, floor_div)}; 97 | 98 | // Stop when the upper bound of x-velocity begins climbing 99 | done = rvx.hi > prev_rvx.hi; 100 | 101 | // Ignore boundaries that increase 102 | rvx = rvx.min(prev_rvx); 103 | 104 | // Add new pairs, subtract overlap with previous iteration 105 | answer += rvx.size() * rvy.size(); 106 | answer -= rvx.intersect(prev_rvx).size() * rvy.intersect(prev_rvy).size(); 107 | 108 | prev_rvx = rvx; 109 | prev_rvy = rvy; 110 | } 111 | 112 | for (int max_n = -ry.hi / n; n < max_n; n++) { 113 | range rvy{ 114 | velocity_bound(ry.lo, n, ceil_div), 115 | velocity_bound(ry.hi, n, floor_div)}; 116 | answer += prev_rvx.size() * (rvy.size() - rvy.intersect(prev_rvy).size()); 117 | prev_rvy = rvy; 118 | } 119 | 120 | auto raw_hyper = hyper_count(ry, prev_rvy.hi + 1); 121 | answer += prev_rvx.size() * raw_hyper; 122 | 123 | return answer; 124 | } 125 | 126 | } 127 | 128 | output_t day17(input_t in) { 129 | range rx{in}; 130 | range ry{in}; 131 | 132 | int part1 = ry.lo * (ry.lo + 1) / 2; 133 | int part2 = day17_part2(rx, ry); 134 | 135 | return { part1, part2 }; 136 | } 137 | -------------------------------------------------------------------------------- /src/day18.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | struct snumber_t { 6 | std::array v; 7 | uint8_t carry_left = 0; 8 | uint8_t carry_right = 0; 9 | 10 | snumber_t() { 11 | } 12 | 13 | snumber_t(input_t &in) : v() { 14 | int step = 16, idx = 0; 15 | for (; *in.s != '\n'; in.s++, in.len--) { 16 | switch (*in.s) { 17 | case '[': step >>= 1; break; 18 | case ']': step <<= 1; break; 19 | case ',': break; 20 | default: 21 | v[idx] = *in.s - '0' + 1; 22 | idx += step; 23 | } 24 | } 25 | } 26 | 27 | void pre_explode() { 28 | uint8_t *left1 = &carry_left, *left2 = NULL; 29 | for (int i = 0; i < 16; i++) { 30 | if (!v[i]) continue; 31 | v[i] += std::exchange(carry_right, 0); 32 | if (i & 1) { 33 | *left2 += std::exchange(v[i - 1], 1) - 1; 34 | carry_right = std::exchange(v[i], 0) - 1; 35 | } else { 36 | left2 = std::exchange(left1, &v[i]); 37 | } 38 | } 39 | for (int i = 1; i < 8; i++) v[i] = v[i * 2]; 40 | for (int i = 0; i < 8; i++) v[i + 8] = v[i]; 41 | } 42 | 43 | int magnitude() const { 44 | std::array M; 45 | for (int i = 0; i < 16; i++) { 46 | M[i] = v[i] - (v[i] != 0); 47 | } 48 | for (int s = 1; s < 16; s <<= 1) { 49 | for (int i = s; i < 16; i += s * 2) { 50 | if (v[i]) M[i - s] += 2 * (M[i - s] + M[i]); 51 | } 52 | } 53 | return M[0]; 54 | } 55 | 56 | snumber_t append(const snumber_t &o) const { 57 | snumber_t s; 58 | s.v = v; 59 | int i; 60 | for (i = 8; i < 16; i++) { 61 | s.v[i] = o.v[i]; 62 | } 63 | if (o.carry_left) { 64 | for (i = 7; !s.v[i]; i--) { } 65 | s.v[i] += o.carry_left; 66 | } 67 | return s; 68 | } 69 | 70 | snumber_t operator + (const snumber_t &o) const { 71 | snumber_t s = append(o); 72 | 73 | for (bool done = false; !done; ) { 74 | done = true; 75 | for (int i = 0; i < 16; i++) { 76 | if (s.v[i] <= 10) continue; 77 | done = false; 78 | 79 | int a = (s.v[i] + 1) / 2, b = (s.v[i] / 2) + 1; 80 | 81 | int bit = ((16 + i) & -(16 + i)); 82 | for (bit >>= 1; bit && s.v[i | bit]; bit >>= 1) { } 83 | 84 | if (bit) { 85 | s.v[i] = a; 86 | s.v[i | bit] = b; 87 | } else { 88 | int idx; 89 | for (idx = i - 1; idx >= 0 && !s.v[idx]; idx--) { } 90 | if (idx >= 0) s.v[idx] += a - 1; 91 | if (i < 15) s.v[i + 1] += b - 1; 92 | s.v[i] = 1; 93 | } 94 | break; 95 | } 96 | } 97 | 98 | return s; 99 | } 100 | }; 101 | 102 | } 103 | 104 | output_t day18(input_t in) { 105 | std::vector V; 106 | for (; in.len > 0; in.s++, in.len--) { 107 | V.emplace_back(in); 108 | } 109 | 110 | auto sum = V[0]; 111 | for (int i = 1; i < V.size(); i++) { 112 | sum.pre_explode(); 113 | auto addend = V[i]; 114 | addend.v[0] += sum.carry_right; 115 | addend.pre_explode(); 116 | sum = sum + addend; 117 | } 118 | int part1 = sum.magnitude(); 119 | 120 | int part2 = 0; 121 | for (int i = 0; i < V.size(); i++) { 122 | auto a = V[i]; 123 | a.pre_explode(); 124 | for (int j = 0; j < V.size(); j++) { 125 | if (i == j) continue; 126 | auto b = V[j]; 127 | b.v[0] += a.carry_right; 128 | b.pre_explode(); 129 | part2 = std::max(part2, (a + b).magnitude()); 130 | } 131 | } 132 | 133 | return { part1, part2 }; 134 | } 135 | -------------------------------------------------------------------------------- /src/day19.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | struct pt { 6 | std::array C; 7 | 8 | pt() : C() { 9 | } 10 | 11 | pt(int x, int y, int z) { 12 | C = { x, y, z }; 13 | } 14 | 15 | pt min(const pt &o) const { 16 | return pt{ 17 | std::min(C[0], o.C[0]), 18 | std::min(C[1], o.C[1]), 19 | std::min(C[2], o.C[2]), 20 | }; 21 | } 22 | 23 | bool operator < (const pt &o) const { 24 | return C < o.C; 25 | } 26 | 27 | uint64_t hash() const { 28 | uint64_t hash = 0; 29 | for (auto n : C) { 30 | hash = (hash << 21) ^ n; 31 | } 32 | return hash; 33 | } 34 | }; 35 | 36 | struct Scanner { 37 | std::vector P; 38 | pt offset; 39 | pt min; 40 | 41 | void add(const pt &p) { 42 | min = min.min(p); 43 | P.push_back(p); 44 | } 45 | }; 46 | 47 | } 48 | 49 | output_t day19(input_t in) { 50 | std::vector scanners = { }; 51 | 52 | while (in.len) { 53 | scanners.emplace_back(); 54 | auto ¤t_scanner = scanners.back(); 55 | parse::skip(in, 18); 56 | do { 57 | int x = parse::integer(in); 58 | int y = parse::integer(in); 59 | int z = parse::integer(in); 60 | current_scanner.add(pt{x, y, z}); 61 | parse::skip(in, 1); 62 | } while (in.len && *in.s != '\n'); 63 | if (!in.len) break; 64 | parse::skip(in, 1); 65 | } 66 | 67 | // TODO cleanup 68 | auto align = [](const Scanner &a, Scanner &b, int aa) { 69 | std::vector collision(4096 * 6); 70 | for (auto pa : a.P) { 71 | for (auto pb : b.P) { 72 | int base = 0; 73 | for (int n : { 74 | 2048 + (pb.C[0] - b.min.C[0]) - (pa.C[aa] - a.min.C[aa]), (pb.C[0] - b.min.C[0]) + (pa.C[aa] - a.min.C[aa]), 75 | 2048 + (pb.C[1] - b.min.C[1]) - (pa.C[aa] - a.min.C[aa]), (pb.C[1] - b.min.C[1]) + (pa.C[aa] - a.min.C[aa]), 76 | 2048 + (pb.C[2] - b.min.C[2]) - (pa.C[aa] - a.min.C[aa]), (pb.C[2] - b.min.C[2]) + (pa.C[aa] - a.min.C[aa]) }) 77 | { 78 | int idx = base + n; 79 | if (++collision[idx] == 12) { 80 | int ori = idx / 4096; 81 | int axis = ori / 2; 82 | int negate = ori % 2; 83 | 84 | n += b.min.C[axis]; 85 | if (negate) { 86 | n += a.min.C[aa]; 87 | } else { 88 | n -= a.min.C[aa] + 2048; 89 | } 90 | 91 | b.offset.C[aa] = negate ? -n : n; 92 | 93 | if (axis != aa) { 94 | std::swap(b.min.C[aa], b.min.C[axis]); 95 | for (auto &p : b.P) { 96 | std::swap(p.C[aa], p.C[axis]); 97 | } 98 | } 99 | if (negate) { 100 | b.min.C[aa] = n - b.min.C[aa] - 2047; 101 | for (auto &p : b.P) { 102 | p.C[aa] = n - p.C[aa]; 103 | } 104 | } else { 105 | b.min.C[aa] = b.min.C[aa] - n; 106 | for (auto &p : b.P) { 107 | p.C[aa] = p.C[aa] - n; 108 | } 109 | } 110 | return true; 111 | } 112 | base += 4096; 113 | } 114 | } 115 | } 116 | return false; 117 | }; 118 | 119 | uint64_t need = (1LL << scanners.size()) - 2; 120 | std::vector todo = { 0 }; 121 | while (!todo.empty()) { 122 | int i = todo.back(); 123 | todo.pop_back(); 124 | for (auto j : bits(need)) { 125 | if (align(scanners[i], scanners[j], 0)) { 126 | align(scanners[i], scanners[j], 1); 127 | align(scanners[i], scanners[j], 2); 128 | need ^= 1LL << j; 129 | todo.push_back(j); 130 | } 131 | } 132 | } 133 | 134 | std::unordered_set P1; 135 | for (auto &s : scanners) { 136 | for (auto p : s.P) { 137 | P1.insert(p.hash()); 138 | } 139 | } 140 | int part1 = P1.size(); 141 | 142 | int part2 = 0; 143 | for (auto &a : scanners) { 144 | for (auto &b : scanners) { 145 | int dist = 0; 146 | dist += std::abs(a.offset.C[0] - b.offset.C[0]); 147 | dist += std::abs(a.offset.C[1] - b.offset.C[1]); 148 | dist += std::abs(a.offset.C[2] - b.offset.C[2]); 149 | part2 = std::max(part2, dist); 150 | } 151 | } 152 | 153 | return { part1, part2 }; 154 | } 155 | -------------------------------------------------------------------------------- /src/day20.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | constexpr int DIM = 100; 6 | 7 | auto input_rules(const char *in) { 8 | // Convert input to single-bit rules, but rearrange the 9 | // index bits from 876543210 to 876_543_210 10 | std::array B; 11 | for (int i = 0, idx = 0; i < 512; i++) { 12 | B[idx] = in[i] & 1; 13 | idx = (idx + 0x88 + 1) & 0x777; 14 | } 15 | return B; 16 | } 17 | 18 | auto convert_rules_to_quads(const auto &B) { 19 | std::array Q; 20 | for (int i = 0; i < Q.size(); i++) { 21 | uint8_t quad = 0; 22 | quad |= B[(i >> 5) & 0x777] << 5; 23 | quad |= B[(i >> 4) & 0x777] << 4; 24 | quad |= B[(i >> 1) & 0x777] << 1; 25 | quad |= B[(i >> 0) & 0x777] << 0; 26 | Q[i] = quad; 27 | } 28 | return Q; 29 | } 30 | 31 | auto input_grid_to_quads(const char *in) { 32 | constexpr int QDIM = DIM / 2; 33 | 34 | std::array grid{}; 35 | 36 | auto row = &grid[0]; 37 | for (int r = 0; r < QDIM; r++) { 38 | for (int c = 0; c < QDIM; c++) { 39 | uint8_t quad = 0; 40 | quad |= (in[0] & 1) << 5; 41 | quad |= (in[1] & 1) << 4; 42 | quad |= (in[101] & 1) << 1; 43 | quad |= (in[102] & 1) << 0; 44 | row[c] = quad; 45 | in += 2; 46 | } 47 | in += DIM + 2; 48 | row += DIM; 49 | } 50 | 51 | return grid; 52 | } 53 | 54 | } 55 | 56 | output_t day20(input_t in) { 57 | if (in.len != 514 + (DIM + 1) * DIM) abort(); 58 | 59 | auto Q = convert_rules_to_quads(input_rules(in.s)); 60 | auto grid = input_grid_to_quads(in.s + 514); 61 | 62 | auto advance = [&grid, &Q](uint16_t xor_in, uint8_t xor_out, int dim) { 63 | std::array prev{}; 64 | auto row = &grid[0]; 65 | for (int r = 0; r < dim; r++) { 66 | uint16_t quad = 0; 67 | for (int c = 0; c < dim; c++) { 68 | quad = (quad & 0x3333) << 2; 69 | quad |= (prev[c] << 8) | row[c]; 70 | prev[c] = row[c]; 71 | row[c] = Q[quad ^ xor_in] ^ xor_out; 72 | } 73 | row += DIM; 74 | } 75 | }; 76 | 77 | auto count = [&grid]() { 78 | int n = 0; 79 | for (auto g : grid) { 80 | n += __builtin_popcount(g); 81 | } 82 | return n; 83 | }; 84 | 85 | // And it even works when infinity doesn't flip-flop 86 | uint16_t xor_in = Q[0] ? 0xffff : 0; 87 | uint8_t xor_out = Q[0] ? 0x33 : 0; 88 | 89 | int step = 0; 90 | 91 | while (step < 2) { 92 | advance(0, xor_out, 50 + (++step)); 93 | advance(xor_in, 0, 50 + (++step)); 94 | } 95 | int part1 = count(); 96 | 97 | while (step < 50) { 98 | advance(0, xor_out, 50 + (++step)); 99 | advance(xor_in, 0, 50 + (++step)); 100 | } 101 | int part2 = count(); 102 | 103 | return { part1, part2 }; 104 | } 105 | -------------------------------------------------------------------------------- /src/day21.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | constexpr int points(int pos) { 6 | return pos + 1; 7 | } 8 | 9 | auto get_skip10(const auto &pos) { 10 | std::array skip = { }; 11 | 12 | skip[0] += 4 * points((pos[0] + 0) % 10); 13 | skip[0] += 4 * points((pos[0] + 2) % 10); 14 | skip[0] += 2 * points((pos[0] + 6) % 10); 15 | 16 | skip[1] += 2 * points((pos[1] + 0) % 10); 17 | skip[1] += 2 * points((pos[1] + 3) % 10); 18 | skip[1] += 1 * points((pos[1] + 4) % 10); 19 | skip[1] += 2 * points((pos[1] + 5) % 10); 20 | skip[1] += 2 * points((pos[1] + 8) % 10); 21 | skip[1] += 1 * points((pos[1] + 9) % 10); 22 | 23 | return skip; 24 | } 25 | 26 | int day21_part1(const auto &start) { 27 | constexpr int FINAL_SCORE = 1000; 28 | 29 | // Get number of points earned by each player in 10 turns 30 | auto skip10 = get_skip10(start); 31 | 32 | // Find the winner and total number of turns played, 33 | // rounded down to the nearest multiple of 20 34 | int winner = skip10[0] < skip10[1]; 35 | int turns = (FINAL_SCORE - 1) / skip10[winner]; 36 | std::array score = { 37 | skip10[0] * turns, 38 | skip10[1] * turns 39 | }; 40 | turns *= 20; 41 | 42 | // Play out the remaining turns 43 | int die = 6 + 9 * turns; 44 | auto pos = start; 45 | for (int i = 0; score[1 - i] < FINAL_SCORE; i = 1 - i) { 46 | pos[i] += die; 47 | score[i] += points(pos[i] % 10); 48 | die += 9; 49 | turns++; 50 | } 51 | 52 | return score[1 - winner] * turns * 3; 53 | } 54 | 55 | struct winlose_t { 56 | int64_t win = 0, lose = 0; 57 | winlose_t swap() const { 58 | return winlose_t{lose, win}; 59 | } 60 | winlose_t operator * (int64_t m) const { 61 | return winlose_t{win * m, lose * m}; 62 | } 63 | winlose_t & operator += (const winlose_t &o) { 64 | win += o.win; 65 | lose += o.lose; 66 | return *this; 67 | } 68 | int64_t max() const { 69 | return std::max(win, lose); 70 | } 71 | }; 72 | 73 | int64_t day21_part2(const auto &start) { 74 | winlose_t DP[21][21][10][10] = { }; 75 | 76 | for (int score_sum = 40; score_sum >= 0; score_sum--) { 77 | int s0 = std::min(score_sum, 20); 78 | for (int s1 = score_sum - s0; s0 >= 0 && s1 <= 20; s0--, s1++) { 79 | for (int p0 = 0; p0 < 10; p0++) { 80 | int next_p0 = (p0 < 7) ? p0 + 3 : p0 - 7; 81 | for (int p1 = 0; p1 < 10; p1++) { 82 | winlose_t WL; 83 | int pos = next_p0; 84 | for (int mul : { 1, 3, 6, 7, 6, 3, 1 }) { 85 | int score = s0 + points(pos); 86 | if (score < 21) { 87 | WL += DP[s1][score][p1][pos] * mul; 88 | } else { 89 | WL += winlose_t{0, 1} * mul; 90 | } 91 | pos = (pos < 9) ? pos + 1 : 0; 92 | } 93 | DP[s0][s1][p0][p1] = WL.swap(); 94 | } 95 | } 96 | } 97 | } 98 | 99 | return DP[0][0][start[0]][start[1]].max(); 100 | } 101 | 102 | } 103 | 104 | output_t day21(input_t in) { 105 | auto points = [](int pos) { 106 | return pos + 1; 107 | }; 108 | 109 | std::array start; 110 | for (auto &s : start) { 111 | parse::skip(in, 28); 112 | s = (parse::positive(in) - 1) % 10; 113 | parse::skip(in, 1); 114 | } 115 | 116 | auto part1 = day21_part1(start); 117 | auto part2 = day21_part2(start); 118 | 119 | return { part1, part2 }; 120 | } 121 | -------------------------------------------------------------------------------- /src/day22.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | constexpr int NUM_PART1 = 20, NUM_PART2 = 400; 6 | constexpr int NUM_TOTAL = NUM_PART1 + NUM_PART2; 7 | 8 | 9 | struct Interval { 10 | int lo, hi; 11 | 12 | Interval() : lo(), hi() { 13 | } 14 | 15 | Interval(input_t &in) { 16 | lo = parse::integer(in); 17 | hi = parse::integer(in) + 1; 18 | } 19 | 20 | Interval(int lo, int hi) : lo(lo), hi(hi) { 21 | } 22 | 23 | bool intersects(const Interval &o) const { 24 | return (lo < o.hi) && (o.lo < hi); 25 | } 26 | 27 | Interval intersect(const Interval &o) const { 28 | return Interval{std::max(lo, o.lo), std::min(hi, o.hi)}; 29 | } 30 | 31 | auto length() const { 32 | return hi - lo; 33 | } 34 | }; 35 | 36 | struct Cuboid { 37 | std::array axis; 38 | 39 | int64_t volume() const { 40 | int64_t vol = 1; 41 | for (auto &r : axis) { 42 | vol *= r.length(); 43 | } 44 | return vol; 45 | } 46 | 47 | bool intersects(const Cuboid &o) const { 48 | for (int i = 0; i < axis.size(); i++) { 49 | if (!axis[i].intersects(o.axis[i])) { 50 | return false; 51 | } 52 | } 53 | return true; 54 | } 55 | 56 | Cuboid intersect(const Cuboid &o) const { 57 | Cuboid r; 58 | for (int i = 0; i < axis.size(); i++) { 59 | r.axis[i] = axis[i].intersect(o.axis[i]); 60 | } 61 | return r; 62 | } 63 | }; 64 | 65 | struct bitset512 { 66 | using T = uint64_t; 67 | 68 | std::array B = { }; 69 | 70 | void set(int n) { 71 | B[n >> 6] |= T(1) << (n & 63); 72 | } 73 | 74 | bitset512 operator & (const bitset512 &o) const { 75 | bitset512 r; 76 | for (int i = 0; i < B.size(); i++) { 77 | r.B[i] = B[i] & o.B[i]; 78 | } 79 | return r; 80 | } 81 | 82 | struct iterator { 83 | const bitset512 &BS; 84 | int idx; 85 | bits chunk; 86 | iterator(auto &BS, int idx, uint64_t chunk) 87 | : BS(BS), idx(idx), chunk(chunk) 88 | { 89 | } 90 | bool operator != (const iterator &o) const { 91 | return idx != o.idx || chunk != o.chunk; 92 | } 93 | int operator * () const { 94 | return *chunk + (idx * 64); 95 | } 96 | iterator& operator++ () { 97 | ++chunk; 98 | while (!chunk.mask && ++idx < 7) { 99 | chunk = BS.B[idx]; 100 | } 101 | return *this; 102 | } 103 | }; 104 | auto begin() const { 105 | int idx = 0; 106 | while (idx < 7 && !B[idx]) idx++; 107 | return iterator{*this, idx, B[idx]}; 108 | } 109 | auto end() const { 110 | return iterator{*this, 7, 0}; 111 | } 112 | }; 113 | 114 | struct Day22 { 115 | std::vector cuboids; 116 | std::vector on; 117 | std::vector BS; 118 | 119 | void add(input_t &in) { 120 | Cuboid c; 121 | on.push_back(in.s[2] != 'f'); 122 | for (int i = 0; i < 3; i++) { 123 | c.axis[i] = Interval{in}; 124 | } 125 | cuboids.push_back(c); 126 | } 127 | 128 | void find_intersections() { 129 | /* Find all intersections with a previous cuboid in the list. 130 | * The cuboids are convex and axis-aligned, so the 1D version 131 | * of Helly's theorem applies. Let A&B mean "A intersects B", 132 | * (A&B and A&C and B&C) => A&B&C 133 | */ 134 | BS.resize(cuboids.size()); 135 | for (int i = 0; i < cuboids.size(); i++) { 136 | for (int j = 0; j < i; j++) { 137 | if (cuboids[i].intersects(cuboids[j])) { 138 | BS[j].set(i); 139 | } 140 | } 141 | } 142 | } 143 | 144 | int64_t intersect_volume(const Cuboid &cuboid, const auto &set) { 145 | int64_t vol = cuboid.volume(); 146 | for (auto idx : set) { 147 | auto common = cuboid.intersect(cuboids[idx]); 148 | vol -= intersect_volume(common, set & BS[idx]); 149 | } 150 | return vol; 151 | } 152 | 153 | auto solve() { 154 | int64_t part1 = 0, part2 = 0; 155 | find_intersections(); 156 | for (int i = 0; i < cuboids.size(); i++) { 157 | if (!on[i]) continue; 158 | part2 += intersect_volume(cuboids[i], BS[i]); 159 | if (i == NUM_PART1 - 1) part1 = part2; 160 | } 161 | return std::make_pair(part1, part2); 162 | } 163 | }; 164 | 165 | } 166 | 167 | output_t day22(input_t in) { 168 | Day22 day22; 169 | 170 | for (int i = 0; i < NUM_TOTAL; i++) { 171 | day22.add(in); 172 | } 173 | 174 | auto [ part1, part2 ] = day22.solve(); 175 | 176 | return { part1, part2 }; 177 | } 178 | -------------------------------------------------------------------------------- /src/day23.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | namespace { 4 | 5 | /* Room contents are packed into a 32-bit integer, 2 bits per location. 6 | * Although each location can be in any of 5 states (A, B, C, D, empty), 7 | * by not introducing gaps between amphipods, we don't need "empty". 8 | */ 9 | constexpr uint32_t ROOM_XOR = 0xffaa5500; 10 | constexpr uint16_t COST[] = { 1, 10, 100, 1000 }; 11 | 12 | // [8*room + hall] 13 | // moves to mask after a given previous move 14 | constexpr uint32_t SafeSkips[] = { 15 | 0, 0, 0, 0, 0, 0, 0, 0, 16 | 0, 0, 0x3, 0x7, 0x7, 0x7, 0x7, 0, 17 | 0, 0, 0x3, 0x707, 0xf0f, 0xf0f, 0xf0f, 0, 18 | 0, 0, 0x3, 0x707, 0xf0f0f, 0x1f1f1f, 0x1f1f1f, 0, 19 | }; 20 | 21 | uint32_t read_input(const char *str) { 22 | auto to_glyph = [](char c) { return c - 'A'; }; 23 | uint32_t room = 0; 24 | for (int i = 0; i < 4; i++, str += 2) { 25 | room |= to_glyph(str[31]) << ((8 * i) + 0); 26 | room |= to_glyph(str[45]) << ((8 * i) + 2); 27 | } 28 | return room ^ (ROOM_XOR & 0x0f0f0f0f); 29 | } 30 | 31 | // Insert the Part 2 occupants into the input rooms 32 | uint32_t insert_part2(uint32_t room) { 33 | room = (room & 0x03030303) | ((room << 4) & 0xc0c0c0c0); 34 | return room ^ 0x1c2c0c3c; 35 | } 36 | 37 | /* Allowing multiple amphipods to occupy a location, how much energy is 38 | * required to solve the starting position? Assumes every amphipod must 39 | * leave its room at least once, which should be true of any input. 40 | */ 41 | auto base_cost(uint32_t room) { 42 | room ^= ROOM_XOR; 43 | auto move_cost = [](int dist) { 44 | return 2 * std::max(1, std::abs(dist)); 45 | }; 46 | int base = 0, second_row = 0; 47 | for (int i = 0; i < 4; i++) { 48 | int glyph0 = room & 3, glyph1 = (room >> 2) & 3; 49 | int cost0 = COST[glyph0], cost1 = COST[glyph1]; 50 | base += cost0 * (move_cost(i - glyph0) + 1); 51 | base += cost1 * move_cost(i - glyph1); 52 | second_row += cost1; 53 | room >>= 8; 54 | } 55 | int cost1 = base + second_row * 2 + 3333; 56 | int cost2 = base + second_row * 4 + 29115; 57 | return std::make_pair(cost1, cost2); 58 | } 59 | 60 | struct Room { 61 | uint32_t room; 62 | 63 | Room(uint32_t room = 0) : room(room) { 64 | } 65 | 66 | operator uint32_t () const { 67 | return room; 68 | } 69 | 70 | bool empty(int r) const { 71 | return !((room >> (8 * r)) & 0xff); 72 | } 73 | 74 | uint32_t get(int r) const { 75 | return r ^ ((room >> (8 * r)) & 3); 76 | } 77 | 78 | void pop(int r) { 79 | uint32_t mask1 = 0xff << (8 * r); 80 | uint32_t mask2 = 0x3f << (8 * r); 81 | room = ((room >> 2) & mask2) | (room & ~mask1); 82 | } 83 | 84 | }; 85 | 86 | struct Hall { 87 | uint32_t hall; 88 | 89 | Hall(uint32_t hall = 0) : hall(hall) { 90 | } 91 | 92 | operator uint32_t () const { 93 | return hall; 94 | } 95 | 96 | bool empty(int h) const { 97 | return !(hall & (4 << (4 * h))); 98 | } 99 | 100 | void clear(int h) { 101 | hall &= ~(0xf << (4 * h)); 102 | } 103 | 104 | void set(int h, int g) { 105 | hall |= (4 | g) << (4 * h); 106 | } 107 | 108 | int get(int h) const { 109 | return (hall >> (4 * h)) & 3; 110 | } 111 | 112 | uint32_t mask() const { 113 | return hall & 0x4444444; 114 | } 115 | }; 116 | 117 | struct State { 118 | Room room; 119 | Hall hall; 120 | 121 | State() : room(), hall() { 122 | } 123 | 124 | State(uint64_t hash) : room(hash), hall(hash >> 32) { 125 | } 126 | 127 | uint64_t hash() const { 128 | return (uint64_t(hall) << 32) | room; 129 | } 130 | 131 | bool solved() const { 132 | return !(room | hall); 133 | } 134 | 135 | static int room_L(int r) { return r + 1; } 136 | 137 | static int room_R(int r) { return r + 2; } 138 | 139 | bool obstructed(int r, int h) const { 140 | int lo, hi; 141 | if (h <= room_L(r)) { 142 | lo = h + 1; 143 | hi = room_L(r); 144 | } else { 145 | lo = room_R(r); 146 | hi = h - 1; 147 | } 148 | uint32_t mask = (16 << (4 * hi)) - (1 << (4 * lo)); 149 | return !!(hall & mask); 150 | } 151 | 152 | bool force_one() { 153 | // can move from hallway to room? 154 | for (auto b : bits(hall.mask())) { 155 | int h = b / 4; 156 | int r = hall.get(h); 157 | if (room.empty(r) && !obstructed(r, h)) { 158 | hall.clear(h); 159 | return true; 160 | } 161 | } 162 | 163 | // can move from room to room? 164 | for (int r = 0; r < 4; r++) { 165 | if (room.empty(r)) continue; 166 | int g = room.get(r); 167 | if (g == r || !room.empty(g)) continue; 168 | if (!obstructed(r, (r < g) ? room_R(g) : room_L(g))) { 169 | room.pop(r); 170 | return true; 171 | } 172 | } 173 | 174 | return false; 175 | } 176 | 177 | bool deadlocked() const { 178 | // Two amphipods in the hallway are deadlocked 179 | // if they must cross each other 180 | uint32_t h43 = hall & 0x0077000; 181 | if (h43 == 0x0047000) return true; 182 | if (h43 == 0x0057000) return true; 183 | uint32_t h42 = hall & 0x0070700; 184 | if (h42 == 0x0040700) return true; 185 | uint32_t h32 = hall & 0x0007700; 186 | if (h32 == 0x0004600) return true; 187 | if (h32 == 0x0004700) return true; 188 | return false; 189 | } 190 | 191 | bool crowded() const { 192 | // A hallway is too crowded if there is not enough 193 | // space to empty out any of the rooms 194 | int h0 = 0, h1 = 0; 195 | uint32_t H = (hall >> 2) | 0x10000000; 196 | bool satisfied = false; 197 | for (int i = 0; i < 8; i++) { 198 | if (H & 1) { 199 | if (h0 < i) { 200 | int r0 = std::max(0, h0 - 2); 201 | int r1 = std::min(3, i - 2); 202 | int space = i - h0; 203 | int mask = 3 << (2 * space); 204 | for (int r = r0; r <= r1; r++) { 205 | uint32_t rr = room >> (8 * r); 206 | rr &= 0xff; 207 | if (!(rr & mask)) { 208 | satisfied = true; 209 | } 210 | } 211 | } 212 | h0 = i + 1; 213 | } 214 | H >>= 4; 215 | } 216 | return !satisfied; 217 | } 218 | 219 | auto neighbors(uint32_t skip = 0) const { 220 | std::vector> N; 221 | 222 | // TODO see if a good ranges implementation can speed things up 223 | 224 | // Amphipod in the hallway between rooms might 225 | // split the board into independent parts, 226 | // so we should impose a move ordering 227 | int skip_rooms = 0; 228 | for (uint32_t i = 0, h = 2; i < 3; i++, h++) { 229 | if (!hall.empty(h)) { 230 | int mask = 0b1110 << i; 231 | skip_rooms |= (i < hall.get(h)) ? ~mask : mask; 232 | } 233 | } 234 | 235 | for (int r = 0; r < 4; r++) { 236 | if (skip_rooms & (1 << r)) continue; 237 | 238 | if (room.empty(r)) continue; 239 | 240 | int g = room.get(r); 241 | int lo, hi; 242 | if (r < g) { 243 | lo = room_R(r); 244 | hi = room_L(g); 245 | } else if (g < r) { 246 | lo = room_R(g); 247 | hi = room_L(r); 248 | } else { 249 | lo = room_L(r); 250 | hi = room_R(r); 251 | } 252 | 253 | for (int h = 0; h < 7; h++) { 254 | // Don't stop along a direct path 255 | if (r != g && h >= lo && h <= hi) continue; 256 | 257 | // Impose ordering on parallel moves 258 | int skip_idx = 8 * r + h; 259 | if ((skip >> skip_idx) & 1) { 260 | continue; 261 | } 262 | 263 | if (!hall.empty(h) || obstructed(r, h)) continue; 264 | int cost = (h < lo) ? lo - h : (hi < h) ? h - hi : 0; 265 | cost *= 2; 266 | cost -= !(!cost | (r == g)) + (!h | (h == 6)); 267 | cost *= 2; 268 | State n = *this; 269 | n.room.pop(r); 270 | n.hall.set(h, g); 271 | if (n.deadlocked()) continue; 272 | uint32_t skips = SafeSkips[skip_idx]; 273 | while (n.force_one()) { 274 | skips = 0; 275 | } 276 | if (n.crowded()) continue; 277 | N.emplace_back(cost * COST[g], n, skips); 278 | } 279 | } 280 | 281 | return N; 282 | } 283 | }; 284 | 285 | template 286 | struct Hash { 287 | std::vector> table; 288 | Hash() : table(Size) { 289 | } 290 | int find(uint64_t key) const { 291 | int idx = key % Size; 292 | while (table[idx].first && table[idx].first != ~key) { 293 | idx++; 294 | idx &= -(idx < Size); 295 | } 296 | return idx; 297 | } 298 | void insert(uint64_t key, TValue value) { 299 | auto idx = find(key); 300 | table[idx] = { ~key, value }; 301 | } 302 | void set(int idx, TValue value) { 303 | table[idx].second = value; 304 | } 305 | TValue & get(int idx) { 306 | return table[idx].second; 307 | } 308 | bool exists(int idx) const { 309 | return table[idx].first != 0; 310 | } 311 | }; 312 | 313 | int solve(State start) { 314 | Hash<14983, std::pair> Cost; 315 | Cost.insert(start.hash(), { }); 316 | 317 | using TQueue = std::pair; 318 | std::priority_queue, std::greater> Q; 319 | Q.emplace(0, start.hash()); 320 | 321 | while (!Q.empty()) { 322 | auto [ queue_cost, cur_hash ] = Q.top(); 323 | Q.pop(); 324 | 325 | auto [ cur_cost, cur_skips ] = Cost.get(Cost.find(cur_hash)); 326 | if (queue_cost != cur_cost) { 327 | continue; 328 | } 329 | 330 | State cur(cur_hash); 331 | if (cur.solved()) { 332 | break; 333 | } 334 | 335 | auto neighbors = cur.neighbors(cur_skips); 336 | for (auto [ delta, state, skips ] : neighbors) { 337 | uint64_t hash = state.hash(); 338 | int new_cost = cur_cost + delta; 339 | 340 | auto new_idx = Cost.find(hash); 341 | auto & [ prev_cost, prev_skips ] = Cost.get(new_idx); 342 | 343 | if (!Cost.exists(new_idx)) { 344 | Cost.insert(hash, { new_cost, skips }); 345 | Q.emplace(new_cost, hash); 346 | } else if (new_cost == prev_cost) { 347 | prev_skips &= skips; 348 | } else if (new_cost < prev_cost) { 349 | prev_cost = new_cost; 350 | prev_skips = skips; 351 | Q.emplace(new_cost, hash); 352 | } 353 | } 354 | } 355 | 356 | return Cost.get(Cost.find(0)).first; 357 | } 358 | 359 | } 360 | 361 | output_t day23(input_t in) { 362 | if (in.len != 66) abort(); 363 | 364 | uint32_t room_p1 = read_input(in.s); 365 | uint32_t room_p2 = insert_part2(room_p1); 366 | auto [ base_cost1, base_cost2 ] = base_cost(room_p1); 367 | 368 | int part1 = base_cost1 + solve(room_p1); 369 | int part2 = base_cost2 + solve(room_p2); 370 | 371 | return { part1, part2 }; 372 | } 373 | -------------------------------------------------------------------------------- /src/day24.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | output_t day24(input_t in) { 4 | std::string part1("99999999999999"); 5 | std::string part2("11111111111111"); 6 | 7 | std::array B, C; 8 | std::vector S; 9 | for (int i = 0; i < 14; i++) { 10 | parse::skip(in, 37); 11 | int8_t a = parse::positive(in); 12 | parse::skip(in, 7); 13 | if (*in.s == '-') { 14 | in.s++; 15 | B[i] = -parse::positive(in); 16 | } else { 17 | B[i] = parse::positive(in); 18 | } 19 | parse::skip(in, 80); 20 | C[i] = parse::positive(in); 21 | parse::skip(in, 17); 22 | 23 | if (a == 1) { 24 | S.push_back(i); 25 | } else { 26 | int j = S.back(); 27 | S.pop_back(); 28 | int delta = B[i] + C[j]; 29 | if (delta < 0) { 30 | part1[i] = '9' + delta; 31 | part2[j] = '1' - delta; 32 | } else { 33 | part1[j] = '9' - delta; 34 | part2[i] = '1' + delta; 35 | } 36 | } 37 | } 38 | 39 | return { part1, part2 }; 40 | } 41 | -------------------------------------------------------------------------------- /src/day25.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2021.h" 2 | 3 | constexpr int WIDTH = 139, HEIGHT = 137; 4 | 5 | namespace { 6 | 7 | struct Cucumber { 8 | // Wasting a uint64_t to give 256-bit SIMD a chance 9 | std::array, HEIGHT> C; 10 | 11 | bool operator != (const Cucumber &o) const { 12 | return C != o.C; 13 | } 14 | 15 | Cucumber operator | (const Cucumber &o) const { 16 | Cucumber result; 17 | for (int r = 0; r < HEIGHT; r++) { 18 | for (int i = 0; i < 4; i++) { 19 | result.C[r][i] = C[r][i] | o.C[r][i]; 20 | } 21 | } 22 | return result; 23 | } 24 | 25 | Cucumber operator & (const Cucumber &o) const { 26 | Cucumber result; 27 | for (int r = 0; r < HEIGHT; r++) { 28 | for (int i = 0; i < 4; i++) { 29 | result.C[r][i] = C[r][i] & o.C[r][i]; 30 | } 31 | } 32 | return result; 33 | } 34 | 35 | Cucumber operator ^ (const Cucumber &o) const { 36 | Cucumber result; 37 | for (int r = 0; r < HEIGHT; r++) { 38 | for (int i = 0; i < 4; i++) { 39 | result.C[r][i] = C[r][i] ^ o.C[r][i]; 40 | } 41 | } 42 | return result; 43 | } 44 | 45 | Cucumber shift_up() const { 46 | Cucumber result; 47 | result.C[HEIGHT - 1] = C[0]; 48 | for (int r = 1; r < HEIGHT; r++) { 49 | result.C[r - 1] = C[r]; 50 | } 51 | return result; 52 | } 53 | 54 | Cucumber shift_down() const { 55 | Cucumber result; 56 | result.C[0] = C[HEIGHT - 1]; 57 | for (int r = 1; r < HEIGHT; r++) { 58 | result.C[r] = C[r - 1]; 59 | } 60 | return result; 61 | } 62 | 63 | Cucumber shift_left() const { 64 | Cucumber result; 65 | for (int r = 0; r < HEIGHT; r++) { 66 | result.C[r][0] = (C[r][0] >> 1) | (C[r][1] << 63); 67 | result.C[r][1] = (C[r][1] >> 1) | (C[r][2] << 63); 68 | result.C[r][2] = (C[r][2] >> 1) | (C[r][0] << 10); 69 | result.C[r][2] &= 0x7ff; 70 | result.C[r][3] = 0; 71 | } 72 | return result; 73 | } 74 | 75 | Cucumber shift_right() const { 76 | Cucumber result; 77 | for (int r = 0; r < HEIGHT; r++) { 78 | result.C[r][0] = (C[r][0] << 1) | (C[r][2] >> 10); 79 | result.C[r][1] = (C[r][1] << 1) | (C[r][0] >> 63); 80 | result.C[r][2] = (C[r][2] << 1) | (C[r][1] >> 63); 81 | result.C[r][2] &= 0x7ff; 82 | result.C[r][3] = 0; 83 | } 84 | return result; 85 | } 86 | }; 87 | 88 | Cucumber advance_right(const Cucumber &D, const Cucumber &R) { 89 | Cucumber result = R.shift_right(); 90 | Cucumber blocked = result & (D | R); 91 | return (result ^ blocked) | blocked.shift_left(); 92 | } 93 | 94 | Cucumber advance_down(const Cucumber &D, const Cucumber &R) { 95 | Cucumber result = D.shift_down(); 96 | Cucumber blocked = result & (D | R); 97 | return (result ^ blocked) | blocked.shift_up(); 98 | } 99 | 100 | } 101 | 102 | output_t day25(input_t in) { 103 | if (in.len != (WIDTH + 1) * HEIGHT) abort(); 104 | 105 | auto to_mask = [](int count, const char *p) { 106 | uint64_t D_mask = 0, R_mask = 0; 107 | for (int i = 0; i < count; i++) { 108 | D_mask |= uint64_t(p[i] == 'v') << i; 109 | R_mask |= uint64_t(p[i] == '>') << i; 110 | } 111 | return std::make_pair(D_mask, R_mask); 112 | }; 113 | 114 | Cucumber D, R; 115 | for (int r = 0; r < HEIGHT; r++) { 116 | auto &D_row = D.C[r], &R_row = R.C[r]; 117 | std::tie(D_row[0], R_row[0]) = to_mask(64, in.s); 118 | std::tie(D_row[1], R_row[1]) = to_mask(64, in.s + 64); 119 | std::tie(D_row[2], R_row[2]) = to_mask(11, in.s + 128); 120 | D_row[3] = R_row[3] = 0; 121 | in.s += WIDTH + 1; 122 | } 123 | 124 | int part1 = 0; 125 | 126 | for (bool done = false; !done; part1++) { 127 | done = true; 128 | Cucumber next_R = advance_right(D, R); 129 | done = done && !(next_R != R); 130 | R = next_R; 131 | Cucumber next_D = advance_down(D, R); 132 | done = done && !(next_D != D); 133 | D = next_D; 134 | } 135 | 136 | return { part1, 0 }; 137 | } 138 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "advent2021.h" 4 | 5 | static constexpr int INPUT_PADDING = 64; 6 | 7 | static const std::vector advent2021 = { 8 | { day01 }, { day02 }, { day03 }, { day04 }, { day05 }, 9 | { day06 }, { day07 }, { day08 }, { day09 }, { day10 }, 10 | { day11 }, { day12 }, { day13 }, { day14 }, { day15 }, 11 | { day16 }, { day17 }, { day18 }, { day19 }, { day20 }, 12 | { day21 }, { day22 }, { day23 }, { day24 }, { day25 } 13 | }; 14 | 15 | static input_t load_input(const std::string &filename); 16 | static void free_input(input_t &input); 17 | 18 | int main() { 19 | double total_time = 0; 20 | 21 | printf(" Time Part 1 Part 2\n"); 22 | printf("========================================================\n"); 23 | for (int day = 1; day <= advent2021.size(); day++) { 24 | auto &A = advent2021[day - 1]; 25 | if (!A.fn) continue; 26 | 27 | char filename[64]; 28 | sprintf(filename, "input/day%02d.txt", day); 29 | 30 | auto input = load_input(filename); 31 | auto t0 = std::chrono::steady_clock::now(); 32 | auto output = A.fn(input); 33 | auto elapsed = std::chrono::steady_clock::now() - t0; 34 | free_input(input); 35 | 36 | double t = 1e-3 * std::chrono::duration_cast(elapsed).count(); 37 | total_time += t; 38 | 39 | printf("Day %02d: %7.f μs %-16s %-16s\n", 40 | day, 41 | t, 42 | output.answer[0].c_str(), 43 | output.answer[1].c_str()); 44 | } 45 | printf("========================================================\n"); 46 | printf("Total: %7.f μs\n", total_time); 47 | 48 | return 0; 49 | } 50 | 51 | input_t load_input(const std::string &filename) { 52 | FILE *fp = fopen(filename.c_str(), "r"); 53 | if (!fp) { 54 | perror(filename.c_str()); 55 | exit(EXIT_FAILURE); 56 | } 57 | 58 | if (fseek(fp, 0, SEEK_END) != 0) { 59 | perror("fseek"); 60 | exit(EXIT_FAILURE); 61 | } 62 | 63 | input_t in; 64 | in.len = ftell(fp); 65 | in.s = new char[in.len + INPUT_PADDING]; 66 | std::fill(in.s + in.len, in.s + in.len + INPUT_PADDING, 0); 67 | 68 | rewind(fp); 69 | if (fread(in.s, in.len, 1, fp) != 1) { 70 | perror("fread"); 71 | exit(EXIT_FAILURE); 72 | } 73 | fclose(fp); 74 | 75 | return in; 76 | } 77 | 78 | void free_input(input_t &input) { 79 | delete[] input.s; 80 | } 81 | -------------------------------------------------------------------------------- /src/numeric.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2021_NUMERIC 2 | #define _ADVENT2021_NUMERIC 3 | 4 | template 5 | static T fastmod(T n, T mod) { 6 | n -= mod & -(n >= mod); 7 | return n; 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/ocr.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2021_OCR_H 2 | #define _ADVENT2021_OCR_H 3 | 4 | constexpr int OCR_WIDTH = 5; 5 | 6 | template 7 | static char ocr(T *p, F test) { 8 | if (test(p, 0, 3)) { 9 | if (test(p, 5, 3)) { 10 | if (test(p, 0, 2)) { 11 | return test(p, 1, 0) ? 'E' : 'Z'; 12 | } else { 13 | return test(p, 1, 2) ? 'K' : 'H'; 14 | } 15 | } else { 16 | if (test(p, 0, 2)) { 17 | return test(p, 0, 0) ? 'F' : 'J'; 18 | } else { 19 | return 'U'; 20 | } 21 | } 22 | } else { 23 | if (test(p, 5, 3)) { 24 | if (test(p, 0, 0)) { 25 | return test(p, 0, 1) ? 'R' : 'L'; 26 | } else { 27 | return test(p, 2, 3) ? 'A' : 'G'; 28 | } 29 | } else { 30 | if (test(p, 5, 1)) { 31 | return test(p, 0, 0) ? 'B' : 'C'; 32 | } else { 33 | return test(p, 0, 1) ? 'P' : 'Y'; 34 | } 35 | } 36 | } 37 | return '?'; 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/parse.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2021_PARSE_H 2 | #define _ADVENT2021_PARSE_H 3 | 4 | namespace parse { 5 | 6 | static void skip(input_t &in, int n) { 7 | if (in.len < n) abort(); 8 | in.s += n, in.len -= n; 9 | } 10 | 11 | template 12 | static T positive(input_t &in) { 13 | bool have = false; 14 | for (T n = 0; in.len; in.s++, in.len--) { 15 | uint8_t d = *in.s - '0'; 16 | if (d <= 9) { 17 | n = 10 * n + d; 18 | have = true; 19 | } else if (have) { 20 | return n; 21 | } 22 | } 23 | return 0; 24 | } 25 | 26 | template 27 | static T integer(input_t &in) { 28 | bool have = false, neg = false; 29 | char prev = 0; 30 | for (T n = 0; in.len; in.s++, in.len--) { 31 | uint8_t d = *in.s - '0'; 32 | if (d <= 9) { 33 | n = 10 * n + d; 34 | neg |= !have & (prev == '-'); 35 | have = true; 36 | } else if (have) { 37 | return neg ? -n : n; 38 | } 39 | prev = *in.s; 40 | } 41 | return 0; 42 | } 43 | 44 | } 45 | 46 | #endif 47 | --------------------------------------------------------------------------------