├── .gitignore ├── CMakeLists.txt ├── README.md ├── input └── README.md └── src ├── advent2020.cpp ├── advent2020.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 ├── memory.h ├── numeric.h └── parse.h /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | *.cmake 4 | Makefile 5 | advent2020 6 | input/[0-9]* 7 | *.sw? 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | project (advent2020) 3 | 4 | set(CMAKE_BUILD_TYPE Release) 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -std=c++17") 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(advent2020 11 | src/main.cpp 12 | src/advent2020.cpp 13 | src/day01.cpp 14 | src/day02.cpp 15 | src/day03.cpp 16 | src/day04.cpp 17 | src/day05.cpp 18 | src/day06.cpp 19 | src/day07.cpp 20 | src/day08.cpp 21 | src/day09.cpp 22 | src/day10.cpp 23 | src/day11.cpp 24 | src/day12.cpp 25 | src/day13.cpp 26 | src/day14.cpp 27 | src/day15.cpp 28 | src/day16.cpp 29 | src/day17.cpp 30 | src/day18.cpp 31 | src/day19.cpp 32 | src/day20.cpp 33 | src/day21.cpp 34 | src/day22.cpp 35 | src/day23.cpp 36 | src/day24.cpp 37 | src/day25.cpp 38 | ) 39 | target_link_libraries(advent2020 m) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # advent2020-fast 2 | 3 | [Advent of Code 2020](https://adventofcode.com/2020/) optimized C++ solutions. 4 | 5 | Here are the timings from an example run on an i9-9980HK CPU laptop. 6 | 7 | Day 01 41 μs 8 | Day 02 13 μs 9 | Day 03 3 μs 10 | Day 04 41 μs 11 | Day 05 2 μs 12 | Day 06 21 μs 13 | Day 07 430 μs 14 | Day 08 9 μs 15 | Day 09 80 μs 16 | Day 10 2 μs 17 | Day 11 264 μs 18 | Day 12 9 μs 19 | Day 13 2 μs 20 | Day 14 1,051 μs 21 | Day 15 153,485 μs 22 | Day 16 45 μs 23 | Day 17 41 μs 24 | Day 18 70 μs 25 | Day 19 26 μs 26 | Day 20 15 μs 27 | Day 21 53 μs 28 | Day 22 104 μs 29 | Day 23 134,652 μs 30 | Day 24 75 μs 31 | Day 25 4 μs 32 | ------------------- 33 | Total: 290,538 μs 34 | 35 | Solutions should work with any puzzle input, provided it is byte-for-byte an exact copy of the file downloaded from Advent of Code. 36 | 37 | This code makes use of SIMD instructions, and requires an x86 CPU that supports the AVX2 instruction set. 38 | 39 | Two of the solutions depend on 2MiB huge pages to speed up random memory access. To enable on Linux: 40 | 41 | sudo sysctl -w vm.nr_hugepages=64 42 | 43 | # Summary of solutions 44 | 45 | Here are a few brief notes about each solution. 46 | 47 | ## Day 1 48 | 49 | Uses a custom implementation of a 2048-bit set, which is used both as a lookup table, and as a way to very quickly sort the list of values. Sorting the list speeds up Part 2 significantly because the distribution of values is biased toward the upper end of the range. Breaking out of the inner loop when the sum becomes too high is especially effective. 50 | 51 | ## Day 2 52 | 53 | Uses SIMD instructions to find the matching characters and convert their positions to a bit mask. 54 | 55 | ## Day 3 56 | 57 | Parses the input lines using SIMD instructions to match the '#' characters and convert to a bit mask. 58 | 59 | ## Day 4 60 | 61 | Field names and eye colors are matched using minimal perfect hash functions. 62 | 63 | As an example, packing the character codes for `cid` into a 24-bit integer gives the value `0x636964`. Applying the function `x % 477 % 8` to that value results in the number `2`. Hashing each of the eight valid field names in this manner results in a unique value in the range 0 through 7. 64 | 65 | ## Day 5 66 | 67 | The character codes for 'L', 'R', 'B', 'F' in binary are: 68 | 69 | R 01010[0]10 70 | L 01001[1]00 71 | B 01000[0]10 72 | F 01000[1]10 73 | 74 | Inverting the bit in the marked position gives exactly the binary digit needed for each input letter. This bit can be extracted easily and in parallel using SIMD instructions. The only catch is the bits are returned in reverse order from what we need; this is resolved using a vector shuffle. 75 | 76 | The [cumulative XOR](https://oeis.org/A003815), which can be computed in `O(1)` time, is used to find the missing number in range. 77 | 78 | ## Day 6 79 | 80 | Bitwise AND and OR for set intersection and union. 81 | 82 | ## Day 7 83 | 84 | Memoized recursion using `unordered_map` to convert bag colors to natural numbers. 85 | 86 | ## Day 8 87 | 88 | A graph problem dressed up in an assembly code costume, solved using memoized depth first search. 89 | 90 | ## Day 9 91 | 92 | Uses a rolling-window lookup table to avoid `O(25^2)` operations per step. 93 | 94 | ## Day 10 95 | 96 | Values are sorted using a 256-bit set (similar to Day 1). Part 2 is solved using bottom-up dynamic programming. 97 | 98 | ## Day 11 99 | 100 | Cells are represented as 4-bit fields within a 256-bit SIMD register. 101 | 102 | In Part 2, distant vertical and diagonal neighbors are summed by making top-down and bottom-up passes over the grid, shifting left/right by 4 bits at a time. Horziontal neighbors are more of a problem because AVX2 does not easily support variable-distance bitwise shifts, and because comparatively little useful work is parallelized in that direction. To compensate, the shift distances needed for each row are determined ahead of time. 103 | 104 | ## Day 12 105 | 106 | Straightforward manipulation of the coordinates. 107 | 108 | ## Day 13 109 | 110 | Solves the system of congruences using modular multiplicative inverse. 111 | 112 | ## Day 14 113 | 114 | The input is converted to a list of writes to individual memory addresses. The list is then sorted using a stable radix sort by address. The most recent write for each address is found by scanning the array in reverse. 115 | 116 | ## Day 15 117 | 118 | Most of the time is spent with the CPU stalled waiting on memory access. To improve performance, the array is allocated using [2MiB huge pages](https://en.wikipedia.org/wiki/Page_%28computer_memory%29#Multiple_page_sizes) to reduce TLB cache pressure. A bitset is also checked before looking up values in the larger array to avoid unnecessary cache misses for numbers not yet encountered. 119 | 120 | ## Day 16 121 | 122 | Creates a mapping from each number to the set of valid fields represented as a bit mask. This is accomplished efficiently by marking the start and end of each subrange, then constructing a prefix sum array in `O(n)` time (n is the range of values, roughly 1000.) The field mapping is solved by process of elimination. 123 | 124 | ## Day 17 125 | 126 | Cells are represented as 4-bit fields in an array of 128-bit integers. Neighbors are counted using [SWAR](https://en.wikipedia.org/wiki/SWAR) [saturating](https://en.wikipedia.org/wiki/Saturation_arithmetic) addition. Ignoring the parallelism, a cell's neighbors can be counted using only `2 * dimensions` additions: `1+1+1=3, 3+3+3=9, 9+9+9=27, 27+27+27=81`. 127 | 128 | Because the seed is symmetric in 1 or 2 dimensions, the automaton evolves symmetrically in those dimensions. Some time is saved by taking advantage of this symmetry to avoid redundant computation. 129 | 130 | ## Day 18 131 | 132 | Uses a stack of integers to evaluate the expressions, with operators represented using negative numbers. 133 | 134 | ## Day 19 135 | 136 | Exploits properties of the language specified by the grammar. Rules 31 and 42 each match a complementary set of 128 eight-letter words. Makes use of SIMD instructions to map each eight-letter sequences to an index into a 256-bit set. 137 | 138 | ## Day 20 139 | 140 | Each tile is represented as an array of four 16-bit integers (edges) and a single 64-bit integer (the 8x8 core). The group of eight transformations are implemented by applying zero, one, or two primitive reflections (horizontal, vertical, diagonal, antidiagonal) to the core. The primitive reflections are done by efficient bitwise operations. 141 | 142 | The lookup table for matching up tile edges also keeps track of the relative orientation of the tiles. This eliminates the need to guess and check; the correct orientation can be applied directly to each tile. 143 | 144 | Sea monsters are identified by a minimal sequence of bitwise shift and AND operations that take advantage of periodicity within the sea monster's shape. 145 | 146 | ## Day 21 147 | 148 | All input words are short enough to be packed into a 64-bit integer using 7 bits for each character. The input has eight different allergens selected from a pool of only nine possibilities; these are mapped to numbers in the range 0-8 using a minimal perfect hash. 149 | 150 | Each ingredient is mapped to a 64-bit integer sliced into 5-bit fields, each field containing the number of times that ingredient is paired with each allergen. An additional 5-bit field counts the total number of times that ingredient occurs. The mapping from ingredients to counts is done using a custom hash table implementation (open addressing, linear probing.) 151 | 152 | ## Day 22 153 | 154 | This one was a challenge to optimize, and I'm still not completely satisfied with the results. The amount of time varies widely depending on the starting shuffle. Although my input is solved in around 100 μs, a more typical time is closer to 3,000 μs with outliers exceeding 15,000 μs. The variance stems from how effectively it can prune subgames based on the rule that Player 1 always wins when in possession of the high card. 155 | 156 | The two hands of cards are stored as circular arrays of size 64. For cycle detection, the game state is represented by a rolling hash using the [BUZ Hash](https://en.wikipedia.org/wiki/Rolling_hash#Cyclic_polynomial) algorithm, which can be updated incrementally as cards are drawn and won. Although unlikely, this can produce an incorrect answer in the event of a hash collision. To mitigate the cost of accessing the `unordered_set` of hashes each round, repeated game state is tested only when the high card is about to be drawn. 157 | 158 | ## Day 23 159 | 160 | The circular sequence of cups is stored as a successor list. Similar to Day 15, this is bound by memory latency and benefits from huge page allocation. 161 | 162 | ## Day 24 163 | 164 | With fewer neighbors to consider in this cellular automaton, the cells can be stored using only 3 bits each. 165 | 166 | Input is parsed using SIMD instructions to find the positions of `n`, `s`, `w`, `e` on each line and convert them to bit masks which can be quickly converted into [axial coordinates](https://www.redblobgames.com/grids/hexagons/#coordinates-axial). 167 | 168 | ## Day 25 169 | 170 | Two techniques are applied to speed up computing the discrete logarithm modulo `20201227`. Because `φ(20201227) = 20201226 = 2 * 3 * 29 * 116099`, the task can be split into four smaller subproblems using the [Pohlig-Hellman algorithm](https://en.wikipedia.org/wiki/Pohlig%E2%80%93Hellman_algorithm). The four answers are then combined using the Chinese Remainder Theorem. 171 | 172 | Each of the four factors is solved by a different method. `2` is a simple equality test. `3` extracts two bits from `n^6733742 % 20201227` that just happen to align perfectly with the result. `29` uses a perfect hash and a SIMD lookup table. Finally, `116099` uses the [baby-step giant-step](https://en.wikipedia.org/wiki/Baby-step_giant-step) algorithm to meet-in-the-middle in `O(sqrt(116099))` time. 173 | -------------------------------------------------------------------------------- /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/advent2020.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | #ifdef _GNU_SOURCE 4 | #include 5 | #include 6 | #endif 7 | 8 | hugemem::hugemem(int _sz) : is_mmap(true), sz(), ptr(NULL) { 9 | static bool warned = false; 10 | constexpr int pagesize = 1 << 21; // 2MB 11 | sz = (_sz + pagesize - 1) & ~(pagesize - 1); 12 | 13 | #ifdef _GNU_SOURCE 14 | ptr = (int64_t *) mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE|MAP_HUGETLB|MAP_HUGE_2MB, -1, 0); 15 | if (ptr == MAP_FAILED) { 16 | ptr = NULL; 17 | } 18 | #endif 19 | if (!ptr) { 20 | is_mmap = false; 21 | ptr = new int64_t[sz / 8]; 22 | if (!warned) { 23 | warned = true; 24 | printf("Warning: Unable to allocate huge pages!\n"); 25 | } 26 | } 27 | } 28 | 29 | hugemem::~hugemem() { 30 | #ifdef _GNU_SOURCE 31 | if (is_mmap) { 32 | munmap(ptr, sz); 33 | return; 34 | } 35 | #endif 36 | delete ptr; 37 | } 38 | -------------------------------------------------------------------------------- /src/advent2020.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2020_H 2 | #define _ADVENT2020_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 "memory.h" 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/bits.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2020_BITS 2 | #define _ADVENT2020_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 _mm_tzcnt_64(mask); } 13 | bits begin() const { return mask; } 14 | bits end() const { return 0; } 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/day01.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct bitset2048 { 6 | using T = uint64_t; 7 | 8 | std::array B = { }; 9 | 10 | void set(int n) { 11 | B[n >> 6] |= T(1) << (n & 63); 12 | } 13 | 14 | bool test(int n) const { 15 | return (B[n >> 6] >> (n & 63)) & 1; 16 | } 17 | 18 | // Return sorted list of members 19 | auto vec(int reserve) const { 20 | std::vector V; 21 | V.reserve(reserve); 22 | int h = 0; 23 | for (auto mask : B) { 24 | for (auto b : bits(mask)) { 25 | V.push_back(h | b); 26 | } 27 | h += 64; 28 | } 29 | return V; 30 | } 31 | }; 32 | 33 | } 34 | 35 | output_t day01(input_t in) { 36 | int part1 = 0, part2 = 0; 37 | 38 | bitset2048 B; 39 | 40 | int n, count = 0; 41 | while ((n = parse::positive(in))) { 42 | if (n >= 2020) continue; 43 | B.set(n); 44 | count++; 45 | } 46 | 47 | // Get the list of numbers in sorted order 48 | auto V = B.vec(count); 49 | 50 | // Part 1: Meet in the middle 51 | for (auto m : V) { 52 | auto n = 2020 - m; 53 | if (B.test(n)) { 54 | part1 = m * n; 55 | break; 56 | } 57 | } 58 | 59 | /* Part 2: Brute force first two, lookup last 60 | * 61 | * This is MUCH faster than the apparent O(n^2) complexity. 62 | * Because the input must have only one solution, the 63 | * distribution is biased heavily toward larger numbers. 64 | */ 65 | for (auto m = V.begin(); m != V.end(); m++) { 66 | for (auto n = std::next(m); n != V.end(); n++) { 67 | int o = 2020 - *m - *n; 68 | // To avoid duplicates: m < n < o 69 | if (o < *n) break; 70 | if (B.test(o)) { 71 | part2 = *m * *n * o; 72 | goto done; 73 | } 74 | } 75 | } 76 | 77 | done: 78 | return { part1, part2 }; 79 | } 80 | -------------------------------------------------------------------------------- /src/day02.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day02(input_t in) { 4 | int part1 = 0, part2 = 0; 5 | 6 | auto eq = [](__m256i v, char c) { 7 | auto eq = _mm256_cmpeq_epi8(v, _mm256_set1_epi8(c)); 8 | return uint32_t(_mm256_movemask_epi8(eq)); 9 | }; 10 | 11 | while (in.len) { 12 | int from = parse::positive(in); 13 | int to = parse::positive(in); 14 | 15 | if (!from || to < from) break; 16 | 17 | // " x: 12345678" 18 | if (in.len < 4 + to) break; 19 | 20 | char c = in.s[1]; 21 | in.s += 3, in.len -= 3; 22 | 23 | // Use SIMD instructions to match characters 24 | __m256i v = _mm256_loadu_si256((__m256i *) in.s); 25 | 26 | // Find the line length 27 | auto len = _mm_tzcnt_32(eq(v, '\n')); 28 | parse::skip(in, len); 29 | 30 | // Find all matching characters on current line 31 | int match = eq(v, c) & ((1 << len) - 1); 32 | 33 | // Part 1: Check if number of matches is in range 34 | int count = _mm_popcnt_u32(match); 35 | part1 += (count >= from) && (count <= to); 36 | 37 | // Part 2: Either or 38 | part2 += 1 & ((match >> from) ^ (match >> to)); 39 | } 40 | 41 | return { part1, part2 }; 42 | } 43 | -------------------------------------------------------------------------------- /src/day03.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day03(input_t in) { 4 | std::vector V; 5 | V.reserve(512); 6 | 7 | // AVX2 - convert 32 bytes of "..#..##...#...\n" to bitmask 8 | auto to_bits = [](char *s) { 9 | __m256i v = _mm256_loadu_si256((__m256i *) s); 10 | v = _mm256_cmpeq_epi8(v, _mm256_set1_epi8('#')); 11 | return _mm256_movemask_epi8(v); 12 | }; 13 | 14 | /* Rows are all length 31. Memory safety depends on the 15 | * NUL-padding byte added by the input reader, in case 16 | * the input is missing a final newline character. 17 | */ 18 | for (; in.len >= 31; in.s += 32, in.len -= 32) { 19 | V.push_back(to_bits(in.s)); 20 | } 21 | 22 | // Count trees using the given (dx,dy) increment 23 | auto tree = [V](int dx, int dy) { 24 | int shift = 0, count = 0; 25 | for (int i = 0; i < V.size(); i += dy) { 26 | count += (V[i] >> shift) & 1; 27 | shift += dx; 28 | shift -= 31 & -(shift >= 31); 29 | } 30 | return count; 31 | }; 32 | 33 | int64_t part1 = tree(3, 1); 34 | int64_t part2 = part1 * tree(1, 1) * tree(5, 1) * tree(7, 1) * tree(1, 2); 35 | 36 | return { part1, part2 }; 37 | } 38 | -------------------------------------------------------------------------------- /src/day04.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | static constexpr int encode2(const char *s) { 4 | return (s[0] << 8) + s[1]; 5 | } 6 | 7 | static constexpr int encode3(const char *s) { 8 | return (encode2(s) << 8) + s[2]; 9 | } 10 | 11 | // Minimal perfect hash of field names 12 | enum { PID, EYR, CID, IYR, HGT, BYR, HCL, ECL }; 13 | static int hash_field(const char *s) { 14 | return encode3(s) % 477 % 8; 15 | } 16 | 17 | // Check eye color 18 | static bool check_ecl(input_t &in) { 19 | // Minimal perfect hash table of eye colors 20 | static constexpr int valid[7] = { 21 | encode3("grn"), encode3("hzl"), encode3("amb"), encode3("blu"), 22 | encode3("gry"), encode3("brn"), encode3("oth") }; 23 | if (in.len < 3) return false; 24 | int ecl = encode3(in.s); 25 | in.s += 3, in.len -= 3; 26 | return (ecl == valid[ecl % 122 % 7]); 27 | } 28 | 29 | // Hex string: #xxxxxx 30 | static bool check_hex(input_t &in, int count) { 31 | if (in.len < count + 1) return false; 32 | if (*in.s != '#') return false; 33 | for (in.s++, in.len--; count--; in.s++, in.len--) { 34 | if (*in.s < '0' || *in.s > 'f') return false; 35 | if (*in.s > '9' && *in.s < 'a') return false; 36 | } 37 | return true; 38 | } 39 | 40 | // Fixed-width positive integer in range 41 | static bool check_range(input_t &in, int digits, int min, int max) { 42 | int n = parse::positive(in, digits, true); 43 | return n >= min && n <= max; 44 | } 45 | 46 | static bool validate(input_t &in, int h) { 47 | int n; 48 | switch (h) { 49 | case BYR: return check_range(in, 4, 1920, 2002); 50 | case IYR: return check_range(in, 4, 2010, 2020); 51 | case EYR: return check_range(in, 4, 2020, 2030); 52 | case PID: return check_range(in, 9, 1, INT32_MAX); 53 | case ECL: return check_ecl(in); 54 | case HCL: return check_hex(in, 6); 55 | case HGT: 56 | n = parse::positive(in); 57 | if (in.len < 2) return false; 58 | in.s += 2, in.len -= 2; 59 | switch (encode2(in.s - 2)) { 60 | case encode2("cm"): 61 | return (n >= 150) && (n <= 193); 62 | case encode2("in"): 63 | return (n >= 59) && (n <= 76); 64 | } 65 | return false; 66 | } 67 | return true; 68 | } 69 | 70 | output_t day04(input_t in) { 71 | if (in.len < 3) abort(); 72 | 73 | int part1 = 0, part2 = 0, have = 0, bad = 0; 74 | 75 | auto check = [&]() { 76 | int complete = (have | (1 << CID)) == 0xff; 77 | part1 += complete; 78 | part2 += complete & !bad; 79 | }; 80 | 81 | in.s += 3, in.len -= 3; 82 | for (int nl = 0; in.len; ) { 83 | char c = *in.s++; 84 | in.len--; 85 | if (c == '\n') { 86 | nl ^= 1; 87 | if (!nl) { 88 | check(); 89 | have = bad = 0; 90 | } 91 | } else if (c == ':') { 92 | nl = 0; 93 | int h = hash_field(in.s - 4); 94 | have |= 1 << h; 95 | bad = bad || !validate(in, h); 96 | } 97 | } 98 | 99 | // Final passport 100 | check(); 101 | 102 | return { part1, part2 }; 103 | } 104 | -------------------------------------------------------------------------------- /src/day05.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | // cumulative xor of 0..n 4 | static int cxor(int n) { 5 | return (n & (((n << 1) & 2) - 1)) ^ ((n >> 1) & 1); 6 | } 7 | 8 | output_t day05(input_t in) { 9 | // SSSE3 10 | auto to_bits = [](char *s) { 11 | __m128i v = _mm_loadu_si128((__m128i *) s); 12 | v = _mm_shuffle_epi8(v, _mm_set_epi64x( 13 | 0x0001, 0x0203040506070809)); 14 | v = _mm_slli_epi64(v, 5); 15 | return ~_mm_movemask_epi8(v) & 0x3ff; 16 | }; 17 | 18 | int min = 0x3ff, max = 0, xor_all = 0; 19 | 20 | for (; in.len >= 10; in.s += 11, in.len -= 11) { 21 | if (*in.s < ' ') in.s++, in.len--; // In case of CRLF 22 | int n = to_bits(in.s); 23 | min = std::min(min, n); 24 | max = std::max(max, n); 25 | xor_all ^= n; 26 | } 27 | 28 | int part1 = max; 29 | int part2 = xor_all ^ cxor(min - 1) ^ cxor(max); 30 | 31 | return { part1, part2 }; 32 | } 33 | -------------------------------------------------------------------------------- /src/day06.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day06(input_t in) { 4 | int part1 = 0, part2 = 0; 5 | 6 | uint32_t all_or = 0, all_and = -1; 7 | 8 | auto check = [&]() { 9 | part1 += _mm_popcnt_u32(all_or); 10 | part2 += _mm_popcnt_u32(all_and); 11 | }; 12 | 13 | while (in.len) { 14 | auto start = in.s; 15 | uint32_t mask = 0; 16 | for (; *in.s >= 'a'; in.s++, in.len--) { 17 | mask |= 1 << (*in.s - 'a'); 18 | } 19 | if (in.s == start) { 20 | check(); 21 | all_or = 0, all_and = -1; 22 | } else { 23 | all_or |= mask, all_and &= mask; 24 | } 25 | if (*in.s == '\r') in.s++, in.len--; 26 | in.s++, in.len--; 27 | } 28 | 29 | // Final group 30 | check(); 31 | 32 | return { part1, part2 }; 33 | } 34 | -------------------------------------------------------------------------------- /src/day07.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct nbag { 6 | int n, bag; 7 | nbag(int n, int bag) : n(n), bag(bag) { } 8 | }; 9 | 10 | struct solver { 11 | std::vector> Out; 12 | std::vector> In; 13 | std::vector Memo; 14 | 15 | void grow() { 16 | Out.emplace_back(); 17 | In.emplace_back(); 18 | } 19 | 20 | void contains(int outer, int n, int inner) { 21 | Out[inner].emplace_back(outer); 22 | In[outer].emplace_back(n, inner); 23 | } 24 | 25 | int part1(int b) { 26 | Memo.resize(Out.size()); 27 | Memo[b] = 0; 28 | return _part1(b); 29 | } 30 | 31 | int part2(int b) { 32 | Memo.resize(Out.size()); 33 | Memo[b] = 0; 34 | return _part2(b); 35 | } 36 | 37 | int _part1(int b) { 38 | auto &M = Memo[b]; 39 | if (M) return 0; 40 | for (auto bag : Out[b]) M += _part1(bag); 41 | return ++M; 42 | } 43 | 44 | int _part2(int b) { 45 | auto &M = Memo[b]; 46 | if (!M) { 47 | M = 1; 48 | for (auto [ n, bag ] : In[b]) { 49 | M += _part2(bag) * n; 50 | } 51 | } 52 | return M; 53 | } 54 | }; 55 | 56 | } 57 | 58 | output_t day07(input_t in) { 59 | std::unordered_map BagIdx; 60 | solver S; 61 | 62 | auto parse_word = [&in](std::string &s) { 63 | while (*in.s >= 'a' && *in.s <= 'z') { 64 | s.push_back(*in.s); 65 | in.s++, in.len--; 66 | } 67 | }; 68 | 69 | auto parse_bag = [&]() { 70 | std::string s; 71 | parse_word(s); 72 | s.push_back(' '); 73 | parse::skip(in, 1); 74 | parse_word(s); 75 | auto [ it, ok ] = BagIdx.emplace(s, BagIdx.size()); 76 | if (ok) S.grow(); 77 | return it->second; 78 | }; 79 | 80 | while (in.len) { 81 | auto outer = parse_bag(); 82 | parse::skip(in, 13); 83 | if (in.s[1] == 'n') { 84 | parse::skip(in, 16); 85 | continue; 86 | } 87 | while (*in.s == ' ') { 88 | parse::skip(in, 1); 89 | int n = parse::positive(in); 90 | parse::skip(in, 1); 91 | int inner = parse_bag(); 92 | parse::skip(in, 5 + (n > 1)); 93 | S.contains(outer, n, inner); 94 | } 95 | parse::skip(in, 1); 96 | } 97 | 98 | std::vector Memo(BagIdx.size()); 99 | auto gold = BagIdx["shiny gold"]; 100 | int part1 = S.part1(gold) - 1; 101 | int part2 = S.part2(gold) - 1; 102 | 103 | return { part1, part2 }; 104 | } 105 | -------------------------------------------------------------------------------- /src/day08.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct instr { 6 | int next, alt, acc; 7 | bool mark; 8 | instr(int next, int alt, int acc) : 9 | next(next), alt(alt), acc(acc), mark() 10 | { 11 | } 12 | }; 13 | 14 | struct solver { 15 | std::vector V; 16 | std::vector> Alt; 17 | 18 | int part1() { 19 | int idx = 0, acc = 0; 20 | while (idx >= 0 && idx <= V.size()) { 21 | auto &I = V[idx]; 22 | if (I.mark) break; 23 | acc += I.acc; 24 | I.mark = true; 25 | idx = I.next; 26 | 27 | // Remember alternate execution path 28 | if (I.alt != I.next) { 29 | Alt.emplace_back(I.alt, acc); 30 | } 31 | } 32 | 33 | return acc; 34 | } 35 | 36 | int part2() { 37 | for (auto [ idx, acc ] : Alt) { 38 | acc = part2_tail(idx, acc); 39 | if (acc) return acc; 40 | } 41 | return 0; 42 | } 43 | 44 | int part2_tail(int idx, int acc) { 45 | while (idx >= 0 && idx < V.size()) { 46 | auto &I = V[idx]; 47 | if (I.mark) return 0; 48 | I.mark = true; 49 | acc += I.acc; 50 | idx = I.next; 51 | } 52 | return acc; 53 | } 54 | }; 55 | 56 | } 57 | 58 | output_t day08(input_t in) { 59 | solver S; 60 | 61 | while (in.len >= 6) { 62 | char op = in.s[0]; 63 | bool neg = (in.s[4] == '-'); 64 | parse::skip(in, 5); 65 | int n = parse::positive(in); 66 | parse::skip(in, 1); 67 | if (neg) n = -n; 68 | 69 | int next = S.V.size() + 1, alt = next, acc = 0; 70 | 71 | if (op == 'a') { 72 | acc = n; 73 | } else { 74 | alt = next + n - 1; 75 | if (op == 'j') std::swap(next, alt); 76 | } 77 | 78 | S.V.emplace_back(next, alt, acc); 79 | } 80 | 81 | int part1 = S.part1(), part2 = S.part2(); 82 | 83 | return { part1, part2 }; 84 | } 85 | -------------------------------------------------------------------------------- /src/day09.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day09(input_t in) { 4 | std::vector V; 5 | 6 | V.reserve(1000); 7 | 8 | int64_t n; 9 | while ((n = parse::positive(in))) { 10 | V.push_back(n); 11 | } 12 | 13 | int64_t part1 = 0, part2 = 0; 14 | 15 | std::unordered_set H; 16 | for (int i = 0; i < 25; i++) { 17 | H.insert(V[i]); 18 | } 19 | for (int i = 25; i < V.size(); i++) { 20 | bool found = false; 21 | for (int j = i - 25; j < i; j++) { 22 | if (H.count(V[i] - V[j])) { 23 | found = true; 24 | break; 25 | } 26 | } 27 | if (!found) { 28 | // Increment to hide from Part 2 29 | part1 = V[i]++; 30 | break; 31 | } 32 | H.erase(V[i - 25]); 33 | H.insert(V[i]); 34 | } 35 | 36 | V.push_back(part1); // Memory safety 37 | 38 | int64_t lo = 0, hi = 0, sum = V[0]; 39 | while (sum != part1) { 40 | if (sum < part1) { 41 | sum += V[++hi]; 42 | } else { 43 | sum -= V[lo++]; 44 | } 45 | } 46 | 47 | int64_t min = INT64_MAX, max = INT64_MIN; 48 | for (int i = lo; i <= hi; i++) { 49 | min = std::min(min, V[i]); 50 | max = std::max(max, V[i]); 51 | } 52 | part2 = min + max; 53 | 54 | return { part1, part2 }; 55 | } 56 | -------------------------------------------------------------------------------- /src/day10.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day10(input_t in) { 4 | std::array B = { }; 5 | 6 | while (in.len) { 7 | uint8_t n = parse::positive(in); 8 | if (n) B[n >> 6] |= uint64_t(1) << (n & 63); 9 | } 10 | 11 | int prev = 0, jolt1 = 0, jolt3 = 1, h = 0; 12 | int64_t f0 = 1, f1 = 0, f2 = 0, tmp; 13 | for (auto mask : B) { 14 | for (auto l : bits(mask)) { 15 | int delta = h + l - prev; 16 | prev = h + l; 17 | switch (delta) { 18 | case 1: 19 | jolt1++; 20 | tmp = f0 + f1 + f2; 21 | f2 = f1, f1 = f0, f0 = tmp; 22 | break; 23 | case 2: 24 | f2 = std::exchange(f0, f0 + f1); 25 | f1 = 0; 26 | break; 27 | case 3: 28 | jolt3++; 29 | f1 = f2 = 0; 30 | break; 31 | } 32 | } 33 | h += 64; 34 | } 35 | 36 | auto part1 = jolt1 * jolt3; 37 | auto part2 = f0; 38 | 39 | return { part1, part2 }; 40 | } 41 | -------------------------------------------------------------------------------- /src/day11.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | // SWAR, 4 bits per cell 4 | 5 | namespace { 6 | 7 | struct row { 8 | std::array<__m256i, 2> R; 9 | 10 | row() : R() { 11 | } 12 | 13 | row(const char *s, int dimx) : R() { 14 | auto S = (uint64_t *) &R[0]; 15 | for (int x = 0; x < dimx; x++) { 16 | if (s[x] != 'L') continue; 17 | S[x >> 4] |= uint64_t(1) << ((x & 15) << 2); 18 | } 19 | } 20 | 21 | int count() const { 22 | int n = 0; 23 | auto S = (uint64_t *) &R[0]; 24 | for (int i = 0; i < 8; i++) { 25 | n += _mm_popcnt_u64(S[i]); 26 | } 27 | return n; 28 | } 29 | 30 | // Set 4-bit fields to 1 if zero, or 0 otherwise 31 | row is_zero() const { 32 | row r = ~*this; 33 | for (auto &m : r.R) { 34 | m = _mm256_and_si256(m, _mm256_srli_epi64(m, 1)); 35 | m = _mm256_and_si256(m, _mm256_srli_epi64(m, 2)); 36 | m = _mm256_and_si256(m, _mm256_set1_epi8(0x11)); 37 | } 38 | return r; 39 | } 40 | 41 | row is_small() const { 42 | row r = ~*this; 43 | for (auto &m : r.R) { 44 | m = _mm256_sub_epi64(m, _mm256_set1_epi8(0x33)); 45 | m = _mm256_srli_epi64(m, 3); 46 | m = _mm256_and_si256(m, _mm256_set1_epi8(0x11)); 47 | } 48 | return r; 49 | } 50 | 51 | row operator+ (const row &o) const { row r = *this; return r += o; } 52 | row operator& (const row &o) const { row r = *this; return r &= o; } 53 | row operator| (const row &o) const { row r = *this; return r |= o; } 54 | row operator<< (int s) const { row r = *this; return r <<= s; } 55 | row operator>> (int s) const { row r = *this; return r >>= s; } 56 | 57 | row operator ~ () const { 58 | row r; 59 | auto it = r.R.begin(); 60 | for (auto &m : R) { 61 | *it++ = _mm256_xor_si256(m, _mm256_set1_epi8(0xff)); 62 | } 63 | return r; 64 | } 65 | 66 | void shift_right_4() { 67 | auto l = _mm256_srli_epi64(R[0], 4), cl = _mm256_slli_epi64(R[0], 60); 68 | auto h = _mm256_srli_epi64(R[1], 4), ch = _mm256_slli_epi64(R[1], 60); 69 | auto ml = _mm256_permute2x128_si256(ch, cl, _MM_SHUFFLE(0,0,0,3)); 70 | auto mh = _mm256_permute2x128_si256(ch, ch, _MM_SHUFFLE(3,0,0,3)); 71 | R[0] = _mm256_or_si256(l, _mm256_alignr_epi8(ml, cl, 8)); 72 | R[1] = _mm256_or_si256(h, _mm256_alignr_epi8(mh, ch, 8)); 73 | } 74 | 75 | template void shift_left() { 76 | const auto s = 16 - (N + 4) / 8; 77 | auto m0 = _mm256_permute2x128_si256(R[0], R[0], _MM_SHUFFLE(0,0,3,0)); 78 | auto m1 = _mm256_permute2x128_si256(R[0], R[1], _MM_SHUFFLE(0,2,0,1)); 79 | R[0] = _mm256_alignr_epi8(R[0], m0, s); 80 | R[1] = _mm256_alignr_epi8(R[1], m1, s); 81 | if (N & 4) shift_right_4(); 82 | } 83 | 84 | template void shift_right() { 85 | const auto s = N / 8; 86 | auto m0 = _mm256_permute2x128_si256(R[1], R[0], _MM_SHUFFLE(0,0,0,3)); 87 | auto m1 = _mm256_permute2x128_si256(R[1], R[1], _MM_SHUFFLE(3,0,0,3)); 88 | R[0] = _mm256_alignr_epi8(m0, R[0], s); 89 | R[1] = _mm256_alignr_epi8(m1, R[1], s); 90 | if (N & 4) shift_right_4(); 91 | } 92 | 93 | row& operator<<= (int sl) { 94 | switch (sl) { 95 | case 4: shift_left< 4>(); break; 96 | case 8: shift_left< 8>(); break; 97 | case 12: shift_left<12>(); break; 98 | case 16: shift_left<16>(); break; 99 | case 20: shift_left<20>(); break; 100 | case 24: shift_left<24>(); break; 101 | case 28: shift_left<28>(); break; 102 | case 32: shift_left<32>(); break; 103 | case 36: shift_left<36>(); break; 104 | case 40: shift_left<40>(); break; 105 | case 44: shift_left<44>(); break; 106 | case 48: shift_left<48>(); break; 107 | case 52: shift_left<52>(); break; 108 | case 56: shift_left<56>(); break; 109 | case 60: shift_left<60>(); break; 110 | case 64: shift_left<64>(); break; 111 | case 68: shift_left<68>(); break; 112 | case 72: shift_left<72>(); break; 113 | default: abort(); 114 | } 115 | return *this; 116 | } 117 | 118 | row& operator>>= (int sr) { 119 | switch (sr) { 120 | case 4: shift_right< 4>(); break; 121 | case 8: shift_right< 8>(); break; 122 | case 12: shift_right<12>(); break; 123 | case 16: shift_right<16>(); break; 124 | case 20: shift_right<20>(); break; 125 | case 24: shift_right<24>(); break; 126 | case 28: shift_right<28>(); break; 127 | case 32: shift_right<32>(); break; 128 | case 36: shift_right<36>(); break; 129 | case 40: shift_right<40>(); break; 130 | case 44: shift_right<44>(); break; 131 | case 48: shift_right<48>(); break; 132 | case 52: shift_right<52>(); break; 133 | case 56: shift_right<56>(); break; 134 | case 60: shift_right<60>(); break; 135 | case 64: shift_right<64>(); break; 136 | case 68: shift_right<68>(); break; 137 | case 72: shift_right<72>(); break; 138 | default: abort(); 139 | } 140 | return *this; 141 | } 142 | 143 | row& operator += (const row &o) { 144 | R[0] = _mm256_add_epi64(R[0], o.R[0]); 145 | R[1] = _mm256_add_epi64(R[1], o.R[1]); 146 | return *this; 147 | } 148 | 149 | row& operator &= (const row &o) { 150 | R[0] = _mm256_and_si256(R[0], o.R[0]); 151 | R[1] = _mm256_and_si256(R[1], o.R[1]); 152 | return *this; 153 | } 154 | 155 | row& operator |= (const row &o) { 156 | R[0] = _mm256_or_si256(R[0], o.R[0]); 157 | R[1] = _mm256_or_si256(R[1], o.R[1]); 158 | return *this; 159 | } 160 | 161 | bool operator != (const row &o) const { 162 | return ~_mm256_movemask_epi8(_mm256_cmpeq_epi64(R[0], o.R[0])) || 163 | ~_mm256_movemask_epi8(_mm256_cmpeq_epi64(R[1], o.R[1])); 164 | } 165 | 166 | bool operator ! () const { 167 | uint32_t nz = 0; 168 | nz |= _mm256_movemask_epi8(_mm256_cmpgt_epi64(R[0], _mm256_setzero_si256())); 169 | nz |= _mm256_movemask_epi8(_mm256_cmpgt_epi64(R[1], _mm256_setzero_si256())); 170 | return !nz; 171 | } 172 | }; 173 | 174 | } 175 | 176 | output_t day11(input_t in) { 177 | char *nl = strchr(in.s, '\n'); 178 | if (!nl || nl == in.s) abort(); 179 | 180 | int dimx = nl - in.s; 181 | if (dimx > 124) abort(); 182 | 183 | std::vector G; 184 | for (int y = 0; in.len >= dimx + 1; y++) { 185 | if (in.s[dimx] != '\n') abort(); 186 | G.emplace_back(in.s, dimx); 187 | parse::skip(in, dimx + 1); 188 | } 189 | 190 | // Neighbor counts 191 | std::vector N(G.size()); 192 | 193 | // Seat locations & inverses 194 | std::vector Seat = G, SeatI; 195 | for (auto &r : G) { 196 | SeatI.push_back(~r); 197 | } 198 | 199 | // Find horizontal shifts for each row, precompute masks 200 | std::vector Shift; 201 | std::vector ShiftMaskL, ShiftMaskR; 202 | for (int i = 0; i < G.size(); i++) { 203 | auto gl = G[i], sl = gl, gr = gl, sr = gl; 204 | 205 | int shift = 0; 206 | while (!!sl) { 207 | shift += 4; 208 | sl >>= 4, sr <<= 4; 209 | 210 | auto ml = gl & sl; 211 | if (!ml) continue; 212 | auto mr = gr & sr; 213 | 214 | sl &= ~ml, gl &= ~ml; 215 | sr &= ~mr, gr &= ~mr; 216 | 217 | Shift.push_back(shift); 218 | ShiftMaskL.push_back(ml); 219 | ShiftMaskR.push_back(mr); 220 | 221 | shift = 0; 222 | } 223 | Shift.push_back(0); 224 | ShiftMaskL.emplace_back(); 225 | ShiftMaskR.emplace_back(); 226 | } 227 | 228 | // Update grid based on neighbor counts 229 | auto update = [&]() { 230 | bool diff = false; 231 | for (int i = 0; i < G.size(); i++) { 232 | auto t = (N[i].is_small() & G[i]) | 233 | (N[i].is_zero() & Seat[i]); 234 | diff |= (t != G[i]); 235 | G[i] = t; 236 | } 237 | return diff; 238 | }; 239 | 240 | // Count all occupied seats 241 | auto count = [&]() { 242 | int n = 0; 243 | for (auto &r : G) n += r.count(); 244 | return n; 245 | }; 246 | 247 | // Part 1 248 | for (bool diff = true; diff; diff = update()) { 249 | N[0] = G[0] + (G[0] << 4) + (G[0] >> 4); 250 | row t = N[0]; 251 | for (int i = 1; i < G.size(); i++) { 252 | N[i] = t; 253 | t = G[i] + (G[i] << 4) + (G[i] >> 4); 254 | N[i] += t, N[i-1] += t; 255 | } 256 | } 257 | int part1 = count(); 258 | 259 | // Part 2 260 | G = Seat; 261 | for (bool diff = true; diff; diff = update()) { 262 | std::fill(N.begin(), N.end(), row{}); 263 | 264 | // Propagate distant neighbors vertically/diagonally 265 | auto vertical = [&](int start, int end, int incr) { 266 | row UL, U, UR; 267 | for (int i = start; i != end; i += incr) { 268 | UL = UL >>= 4, UR <<= 4; 269 | N[i] += UL + U + UR; 270 | UL = G[i] | (UL & SeatI[i]); 271 | U = G[i] | (U & SeatI[i]); 272 | UR = G[i] | (UR & SeatI[i]); 273 | } 274 | }; 275 | 276 | vertical(0, G.size(), 1); // Top-down 277 | vertical(G.size() - 1, -1, -1); // Bottom-up 278 | 279 | // Propagate horizontally 280 | for (int i = 0, sidx = 0; i < G.size(); i++, sidx++) { 281 | row L = G[i], R = G[i]; 282 | for (auto s = Shift[sidx]; s; s = Shift[++sidx]) { 283 | L >>= s, R <<= s; 284 | N[i] += (L & ShiftMaskL[sidx]); 285 | N[i] += (R & ShiftMaskR[sidx]); 286 | } 287 | } 288 | } 289 | int part2 = count(); 290 | 291 | return { part1, part2 }; 292 | } 293 | -------------------------------------------------------------------------------- /src/day12.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct pt { 6 | int x, y; 7 | pt() : x(), y() { } 8 | pt(int x, int y) : x(x), y(y) { } 9 | pt operator* (int n) const { return { n*x, n*y }; } 10 | pt& operator+= (const pt &o) { x += o.x, y += o.y; return *this; } 11 | operator int () const { return std::abs(x) + std::abs(y); } 12 | void left() { *this = { -y, x }; } 13 | void right() { *this = { y, -x }; } 14 | void back() { *this = { -x, -y }; } 15 | void north(int n) { y += n; } 16 | void east(int n) { x += n; } 17 | }; 18 | 19 | } 20 | 21 | output_t day12(input_t in) { 22 | pt p1, d1{1,0}, p2, d2{10,1}; 23 | 24 | while (in.len) { 25 | char c = *in.s++; in.len--; 26 | auto n = parse::positive(in); 27 | parse::skip(in, 1); 28 | switch (c) { 29 | case 'S': n = -n; 30 | case 'N': p1.north(n), d2.north(n); break; 31 | 32 | case 'W': n = -n; 33 | case 'E': p1.east(n), d2.east(n); break; 34 | 35 | case 'F': p1 += d1 * n, p2 += d2 * n; break; 36 | 37 | case 'R': n = 360 - n; 38 | case 'L': switch (n) { 39 | case 90: d1.left(), d2.left(); break; 40 | case 180: d1.back(), d2.back(); break; 41 | case 270: d1.right(), d2.right(); break; 42 | } 43 | } 44 | } 45 | 46 | int part1 = p1, part2 = p2; 47 | 48 | return { part1, part2 }; 49 | } 50 | -------------------------------------------------------------------------------- /src/day13.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day13(input_t in) { 4 | auto start = parse::positive(in); 5 | parse::skip(in, 1); 6 | 7 | int64_t part1 = 0, part2 = 0, min_wait = INT64_MAX, prod = 1; 8 | 9 | for (int idx = 0; in.len; idx++) { 10 | if (*in.s == 'x') { 11 | parse::skip(in, 2); 12 | continue; 13 | } 14 | 15 | auto mod = parse::positive(in); 16 | parse::skip(in, 1); 17 | 18 | // Part 1 19 | auto wait = fastmod(mod - start % mod, mod); 20 | if (wait == mod) wait = 0; 21 | if (wait < min_wait) { 22 | part1 = mod * wait; 23 | min_wait = wait; 24 | } 25 | 26 | // Part 2 27 | part2 -= modinv(prod, mod) * (part2 + idx) % mod * prod; 28 | prod *= mod; 29 | } 30 | 31 | // Fixup Part 2 if it's negative 32 | part2 += prod & -(part2 < 0); 33 | 34 | return { part1, part2 }; 35 | } 36 | -------------------------------------------------------------------------------- /src/day14.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct cell { 6 | int64_t addr, value; 7 | cell() { } 8 | cell(int64_t addr, int64_t value) : addr(addr), value(value) { } 9 | }; 10 | 11 | } 12 | 13 | output_t day14(input_t in) { 14 | uint64_t mask0 = 0, mask1 = 0; 15 | 16 | std::vector> P1; 17 | std::vector P2; 18 | P2.reserve(100000); 19 | 20 | while (in.len > 5) { 21 | if (in.s[1] == 'a') { 22 | parse::skip(in, 7); 23 | mask0 = mask1 = 0; 24 | for (int i = 0; i < 36; i++) { 25 | mask0 <<= 1, mask1 <<= 1; 26 | if (in.s[i] == '0') { 27 | mask0++; 28 | } else if (in.s[i] == '1') { 29 | mask1++; 30 | } 31 | } 32 | parse::skip(in, 37); 33 | } else if (in.s[1] == 'e') { 34 | parse::skip(in, 4); 35 | auto addr = parse::positive(in); 36 | parse::skip(in, 4); 37 | auto value = parse::positive(in); 38 | parse::skip(in, 1); 39 | 40 | P1.emplace_back(addr, (value & ~mask0) | mask1); 41 | 42 | uint64_t floating = 0, keep = mask0 | mask1; 43 | uint64_t flot = 0xfffffffff ^ keep; 44 | addr = (addr & mask0) | mask1; 45 | do { 46 | P2.emplace_back(addr + floating, value); 47 | floating = (floating + keep + 1) & flot; 48 | } while (floating); 49 | } else { 50 | break; 51 | } 52 | } 53 | 54 | int64_t part1 = 0, part2 = 0; 55 | 56 | std::bitset<65536> B; 57 | for (int i = P1.size() - 1; i >= 0; i--) { 58 | auto [ addr, value ] = P1[i]; 59 | if (!B.test(addr)) { 60 | B.set(addr); 61 | part1 += value; 62 | } 63 | } 64 | 65 | // Stable sort 66 | std::vector T(P2.size()); 67 | std::array hist; 68 | for (int s = 0; s < 36; s += 12) { 69 | hist.fill(0); 70 | for (auto &c : P2) { 71 | hist[(c.addr >> s) & 0xfff]++; 72 | } 73 | int idx = 0; 74 | for (auto &h : hist) idx += std::exchange(h, idx); 75 | for (auto &c : P2) { 76 | T[hist[(c.addr >> s) & 0xfff]++] = c; 77 | } 78 | P2.swap(T); 79 | } 80 | 81 | int64_t prev = 0; 82 | for (int i = P2.size() - 1; i >= 0; i--) { 83 | auto [ addr, value ] = P2[i]; 84 | if (addr != prev) { 85 | part2 += value; 86 | prev = addr; 87 | } 88 | } 89 | 90 | return { part1, part2 }; 91 | } 92 | -------------------------------------------------------------------------------- /src/day15.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day15(input_t in) { 4 | std::vector seed; 5 | 6 | while (in.len) { 7 | seed.push_back(parse::positive(in)); 8 | parse::skip(in, 1); 9 | } 10 | 11 | constexpr int LIMIT1 = 2020, LIMIT2 = 30000000; 12 | 13 | // Try to get 2MB pages if possible 14 | hugemem alloc(LIMIT2 * sizeof(int)); 15 | auto last = (int *) alloc.ptr; 16 | 17 | auto cur = seed.back(), i = 1; 18 | 19 | // Use standard pages to avoid competing for limited 2MB TLB entries 20 | auto filter = new std::bitset; 21 | 22 | auto step = [&] { 23 | if (cur < (i >> 6)) { 24 | // Skip the filter for small values 25 | cur = std::exchange(last[cur], i); 26 | if (cur) cur = i - cur; 27 | } else if (filter->test(cur)) { 28 | cur = i - std::exchange(last[cur], i); 29 | } else { 30 | filter->set(cur); 31 | last[cur] = i; 32 | cur = 0; 33 | } 34 | }; 35 | 36 | seed.pop_back(); 37 | for (auto k : seed) filter->set(k), last[k] = i++; 38 | 39 | for (; i < LIMIT1; i++) step(); 40 | int part1 = cur; 41 | 42 | for (; i < LIMIT2; i++) step(); 43 | int part2 = cur; 44 | 45 | delete filter; 46 | 47 | return { part1, part2 }; 48 | } 49 | -------------------------------------------------------------------------------- /src/day16.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct range { 6 | int start, end; 7 | range() : start(), end() { } 8 | range(input_t &in) { 9 | start = parse::positive(in) & 1023; 10 | end = (parse::positive(in) + 1) & 1023; 11 | } 12 | }; 13 | 14 | struct range_pair { 15 | std::array R; 16 | range_pair(input_t &in) { 17 | R[0] = in; 18 | R[1] = in; 19 | } 20 | }; 21 | 22 | static auto parse_list(input_t &in) { 23 | std::vector V; 24 | do { 25 | V.push_back(parse::positive(in) & 1023); 26 | } while (*in.s == ','); 27 | return V; 28 | } 29 | 30 | } 31 | 32 | output_t day16(input_t in) { 33 | std::vector R; 34 | while (*in.s >= 'a') { 35 | R.emplace_back(in); 36 | parse::skip(in, 1); 37 | } 38 | 39 | int n_fields = R.size(); 40 | 41 | // Construct mask of valid fields for each value 42 | std::vector valid(1024); 43 | for (int i = 0; i < n_fields; i++) { 44 | uint32_t m = 1 << i; 45 | for (auto &r : R[i].R) { 46 | valid[r.start] += m; 47 | valid[r.end] -= m; 48 | } 49 | } 50 | std::partial_sum(valid.begin(), valid.end(), valid.begin()); 51 | 52 | // Your ticket 53 | auto Your = parse_list(in); 54 | 55 | std::vector M(n_fields, -1); 56 | 57 | int part1 = 0; 58 | 59 | // Nearby tickets 60 | while (in.len > 40) { 61 | auto N = parse_list(in); 62 | N.resize(n_fields); 63 | 64 | // Check ticket validity 65 | bool bad = false; 66 | for (auto k : N) { 67 | bad |= !valid[k]; 68 | part1 += k & -!valid[k]; 69 | } 70 | 71 | if (bad) continue; 72 | 73 | // Update field validity masks 74 | auto it = M.begin(); 75 | for (auto k : N) *it++ &= valid[k]; 76 | } 77 | 78 | // Tag each field mask with position and population count 79 | for (int i = 0; i < n_fields; i++) { 80 | M[i] = (M[i] << 16) | (i << 8) | _mm_popcnt_u64(M[i]); 81 | } 82 | 83 | // Solve position-to-field mapping 84 | std::vector F(n_fields); 85 | for (int f = 0; f < n_fields; f++) { 86 | // Find a singleton 87 | int s; 88 | for (s = 0; s < M.size() && uint8_t(M[s]) != 1; s++) { } 89 | if (s == M.size()) abort(); 90 | 91 | // Record the field number, then remove from the list 92 | auto m = M[s]; 93 | F[_mm_tzcnt_64(m >> 16)] = uint8_t(m >> 8); 94 | M[s] = M.back(); 95 | M.pop_back(); 96 | 97 | // Remove the field from the remaining masks 98 | m = ~m | 0xffff; 99 | for (auto &k : M) { 100 | auto old = std::exchange(k, k & m); 101 | k -= (k != old); // decrement popcount 102 | } 103 | } 104 | 105 | // Departure fields are always the first six 106 | int64_t part2 = 1; 107 | for (int i = 0; i < 6; i++) { 108 | part2 *= Your[F[i]]; 109 | } 110 | 111 | return { part1, part2 }; 112 | } 113 | -------------------------------------------------------------------------------- /src/day17.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | // Masks: 0x11111..., 0x77777..., 0x88888..., 0xccccc... 6 | constexpr auto M1 = __uint128_t(-1) / 15; 7 | constexpr auto M7 = M1 * 7, M8 = M1 * 8, Mc = M1 * 12; 8 | 9 | // Saturating add 4-bit fields (max value 8) 10 | __uint128_t sadd(__uint128_t x, __uint128_t y) { 11 | auto s = (x + (y & M7)) | (y & M8); 12 | auto h = M8 | ((s >> 3) & M1); 13 | return s & (h - M1); 14 | } 15 | 16 | // 3D solver 17 | struct solver1 { 18 | std::array,8> _G{}, _H{}, _I{}; 19 | 20 | int y_min = 6, y_max = 14, z_max = 1; 21 | 22 | auto & G(int z, int y) { return _G[z][y]; } 23 | auto & H(int z, int y) { return _H[z][y]; } 24 | auto & I(int z, int y) { return _I[z][y]; } 25 | 26 | void step() { 27 | // x-sum 28 | for (auto z = 0; z < z_max; z++) { 29 | for (int y = y_min; y < y_max; y++) { 30 | auto r = G(z,y); 31 | H(z,y) = sadd(G(z,y), sadd(r<<4, r>>4)); 32 | } 33 | } 34 | 35 | // y-sum 36 | for (auto z = 0; z < z_max; z++) { 37 | __uint128_t prev = 0; 38 | I(z,y_min-1) = H(z,y_min); 39 | for (auto y = y_min; y < y_max; y++) { 40 | auto r = H(z,y); 41 | I(z,y) = sadd(r, sadd(prev, H(z,y+1))); 42 | prev = r; 43 | } 44 | I(z,y_max) = prev; 45 | } 46 | y_min--, y_max++; 47 | 48 | // z-sum 49 | z_max++; 50 | for (auto z = 0; z < z_max - 1; z++) { 51 | for (auto y = y_min; y < y_max; y++) { 52 | H(z,y) = sadd(I(z,y), sadd(I(std::abs(z-1),y), I(z+1,y))); 53 | } 54 | } 55 | for (auto y = y_min; y < y_max; y++) { 56 | H(z_max-1,y) = I(z_max-2,y); 57 | } 58 | 59 | // transition rule 60 | for (auto z = 0; z < z_max; z++) { 61 | for (int y = y_min; y < y_max; y++) { 62 | auto r = (H(z,y) - G(z,y)) | G(z,y); 63 | r ^= Mc, r &= r >> 2, r &= r >> 1, r &= M1; 64 | G(z,y) = r; 65 | } 66 | } 67 | } 68 | 69 | int count() { 70 | int c = 0; 71 | for (int z = 0; z < z_max; z++) { 72 | for (int y = y_min; y < y_max; y++) { 73 | int add = 0; 74 | auto r = G(z,y); 75 | add += _mm_popcnt_u64(uint64_t(r)); 76 | add += _mm_popcnt_u64(uint64_t(r>>64)); 77 | add <<= !!z; 78 | c += add; 79 | } 80 | } 81 | return c; 82 | } 83 | }; 84 | 85 | // 4D solver 86 | struct solver2 { 87 | std::array,8>,8> _G{}, _H{}, _I{}; 88 | 89 | int y_min = 6, y_max = 14, z_max = 1, w_max = 1; 90 | 91 | auto & G(int w, int z, int y) { return _G[w][z][y]; } 92 | auto & H(int w, int z, int y) { return _H[w][z][y]; } 93 | auto & I(int w, int z, int y) { return _I[w][z][y]; } 94 | 95 | void step() { 96 | // x-sum 97 | for (auto w = 0; w < w_max; w++) { 98 | for (auto z = w; z < z_max; z++) { 99 | for (int y = y_min; y < y_max; y++) { 100 | auto r = G(w,z,y); 101 | H(w,z,y) = sadd(G(w,z,y), sadd(r<<4, r>>4)); 102 | } 103 | } 104 | } 105 | 106 | // y-sum 107 | for (auto w = 0; w < w_max; w++) { 108 | for (auto z = w; z < z_max; z++) { 109 | __uint128_t prev = 0; 110 | I(w,z,y_min-1) = H(w,z,y_min); 111 | for (auto y = y_min; y < y_max; y++) { 112 | auto r = H(w,z,y); 113 | I(w,z,y) = sadd(r, sadd(prev, H(w,z,y+1))); 114 | prev = r; 115 | } 116 | I(w,z,y_max) = prev; 117 | } 118 | } 119 | y_min--, y_max++; 120 | 121 | // z-sum 122 | for (auto y = y_min; y < y_max; y++) { 123 | for (auto w = 0; w < w_max; w++) { 124 | auto prev = w ? I(w-1,w,y) : I(0,1,y); 125 | for (auto z = w; z < z_max; z++) { 126 | auto r = I(w,z,y); 127 | H(w,z,y) = sadd(r, sadd(prev, I(w,z+1,y))); 128 | prev = r; 129 | } 130 | H(w,z_max,y) = prev; 131 | if (w) H(w,w-1,y) = sadd(I(w-1,w,y), sadd(I(std::abs(w-2),w,y), I(w,w,y))); 132 | } 133 | } 134 | z_max++; 135 | 136 | // w-sum 137 | for (auto y = y_min; y < y_max; y++) { 138 | for (auto z = 0; z < z_max; z++) { 139 | __uint128_t prev = H(1,z,y); 140 | for (auto w = 0; w <= z; w++) { 141 | auto r = H(w,z,y); 142 | I(w,z,y) = sadd(r, sadd(prev, H(w+1,z,y))); 143 | prev = r; 144 | } 145 | } 146 | } 147 | w_max++; 148 | 149 | // transition rule 150 | for (auto w = 0; w < w_max; w++) { 151 | for (auto z = w; z < z_max; z++) { 152 | for (int y = y_min; y < y_max; y++) { 153 | auto r = (I(w,z,y) - G(w,z,y)) | G(w,z,y); 154 | r ^= Mc, r &= r >> 2, r &= r >> 1, r &= M1; 155 | G(w,z,y) = r; 156 | } 157 | } 158 | } 159 | } 160 | 161 | int count() { 162 | int c = 0; 163 | for (int w = 0; w < w_max; w++) { 164 | for (int z = w; z < z_max; z++) { 165 | for (int y = y_min; y < y_max; y++) { 166 | int add = 0; 167 | auto r = G(w,z,y); 168 | add += _mm_popcnt_u64(uint64_t(r)); 169 | add += _mm_popcnt_u64(uint64_t(r>>64)); 170 | add <<= !!z + !!w + (w != z); 171 | c += add; 172 | } 173 | } 174 | } 175 | return c; 176 | } 177 | }; 178 | 179 | } 180 | 181 | output_t day17(input_t in) { 182 | solver1 S1; 183 | solver2 S2; 184 | 185 | __uint128_t row = 0; 186 | int y = 0; 187 | for (; in.len--; in.s++) { 188 | if (*in.s == '\n') { 189 | if (y >= 8) abort(); 190 | S1.G( 0,S1.y_min+y) = row << 24; 191 | S2.G(0,0,S2.y_min+y) = row << 24; 192 | row = 0; 193 | y++; 194 | } else { 195 | row = (row << 4) | (*in.s == '#'); 196 | } 197 | } 198 | 199 | for (int i = 0; i < 6; i++) S1.step(); 200 | int part1 = S1.count(); 201 | 202 | for (int i = 0; i < 6; i++) S2.step(); 203 | int part2 = S2.count(); 204 | 205 | return { part1, part2 }; 206 | } 207 | -------------------------------------------------------------------------------- /src/day18.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | using int_t = int64_t; 4 | 5 | namespace { 6 | 7 | template 8 | struct solver { 9 | std::vector S = { -1 }; 10 | 11 | void push(int_t t) { 12 | if (t == -7) t = result(), pop(); 13 | if (t >= 0 && S.back() == -5) pop(), t += pop(); 14 | else if (P1 && t >= 0 && S.back() >= 0) t *= pop(); 15 | S.push_back(t); 16 | } 17 | 18 | auto result() { 19 | auto t = pop(); 20 | while (!P1 && S.back() >= 0) t *= pop(); 21 | return t; 22 | } 23 | 24 | auto pop() { 25 | auto t = S.back(); 26 | S.pop_back(); 27 | return t; 28 | } 29 | }; 30 | 31 | } 32 | 33 | output_t day18(input_t in) { 34 | solver S1; 35 | solver S2; 36 | 37 | int_t part1 = 0, part2 = 0; 38 | 39 | while (in.len) { 40 | int c = *in.s; 41 | if (c == '\n') { 42 | part1 += S1.result(), part2 += S2.result(); 43 | } else if (1 & (0x3ff0b00 >> (c &= 31))) { 44 | c -= 16, S1.push(c), S2.push(c); 45 | } 46 | parse::skip(in, 1); 47 | } 48 | 49 | return { part1, part2 }; 50 | } 51 | -------------------------------------------------------------------------------- /src/day19.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct node { 6 | std::array V = { }; 7 | }; 8 | 9 | struct word { 10 | uint8_t mask; 11 | int8_t len; 12 | 13 | word(uint8_t mask, int8_t len) : mask(mask), len(len) { } 14 | 15 | word operator+ (const word &o) const { 16 | return word((o.mask << len) | mask, len + o.len); 17 | } 18 | }; 19 | 20 | struct solver { 21 | std::array N = { }; 22 | std::array, 256> W = { }; 23 | 24 | solver() { 25 | W[0].emplace_back(0, 0); 26 | } 27 | 28 | void set(int idx, int offset, int c) { 29 | N[idx].V[offset] = c; 30 | } 31 | 32 | void build_dict(int idx) { 33 | if (!W[idx].empty()) return; 34 | 35 | auto &n = N[idx]; 36 | if (!n.V[0]) { 37 | W[idx].emplace_back(n.V[1], 1); 38 | } else { 39 | for (int i = 0; i < 4 && n.V[i]; i += 2) { 40 | build_dict(n.V[i]); 41 | build_dict(n.V[i+1]); 42 | for (auto a : W[n.V[i]]) { 43 | for (auto b : W[n.V[i+1]]) { 44 | W[idx].emplace_back(a + b); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | std::pair solve(input_t &in) { 52 | // Rule 31 is the complement of Rule 42 53 | std::bitset<256> w42; 54 | build_dict(42); 55 | for (auto w : W[42]) w42.set(w.mask); 56 | 57 | int part1 = 0, part2 = 0; 58 | 59 | while (in.len >= 8) { 60 | int n_42 = 0, n_31 = 0, state = 0; 61 | 62 | while (*in.s >= 'a') { 63 | int t = w42.test(parse::octet(in, 7)); 64 | n_42 += t, n_31 += !t; 65 | state += (!t & !state) | (t & state); 66 | } 67 | parse::skip(in, 1); 68 | 69 | int ok = (state == 1) & (n_31 < n_42); 70 | part1 += ok & (n_42 == 2); 71 | part2 += ok; 72 | } 73 | 74 | return { part1, part2 }; 75 | } 76 | }; 77 | 78 | } 79 | 80 | output_t day19(input_t in) { 81 | solver S; 82 | 83 | while (*in.s >= ' ') { 84 | int idx = parse::positive(in) & 255; 85 | 86 | parse::skip(in, 2); 87 | if (*in.s == '"') { 88 | S.set(idx, 1, in.s[1] & 1); 89 | parse::skip(in, 4); 90 | } else { 91 | for (int i = 0; *in.s >= ' '; i ^= 1) { 92 | i ^= (i | 2) & -(in.s[1] == '|'); 93 | S.set(idx, i, parse::positive(in)); 94 | } 95 | parse::skip(in, 1); 96 | } 97 | } 98 | parse::skip(in, 1); 99 | 100 | auto [ part1, part2 ] = S.solve(in); 101 | 102 | return { part1, part2 }; 103 | } 104 | -------------------------------------------------------------------------------- /src/day20.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | // Bitwise transformations 6 | struct xf { 7 | // Reverse 10-bit number 8 | static uint16_t rev(uint16_t b) { 9 | b = (b & 0x00ff) << 8 | (b & 0xff00) >> 8; 10 | b = (b & 0x0f0f) << 4 | (b & 0xf0f0) >> 4; 11 | b = (b & 0x3333) << 2 | (b & 0xcccc) >> 2; 12 | b = (b & 0x5555) << 1 | (b & 0xaaaa) >> 1; 13 | return b >> 6; 14 | } 15 | 16 | // Reflect across diagonal 17 | static uint64_t diag(uint64_t b) { 18 | b = ss<28, 0x00000000f0f0f0f0>(b); 19 | b = ss<14, 0x0000cccc0000cccc>(b); 20 | b = ss< 7, 0x00aa00aa00aa00aa>(b); 21 | return b; 22 | } 23 | 24 | // Reflect across antidiagonal 25 | static uint64_t anti(uint64_t b) { 26 | b = ss<36, 0x000000000f0f0f0f>(b); 27 | b = ss<18, 0x0000333300003333>(b); 28 | b = ss< 9, 0x0055005500550055>(b); 29 | return b; 30 | } 31 | 32 | // Reflect east-west 33 | static uint64_t ew(uint64_t b) { 34 | b = ss<4, 0x0f0f0f0f0f0f0f0f>(b); 35 | b = ss<2, 0x3333333333333333>(b); 36 | b = ss<1, 0x5555555555555555>(b); 37 | return b; 38 | } 39 | 40 | // Reflect north-south 41 | static uint64_t ns(uint64_t b) { 42 | b = ss<32, 0x00000000ffffffff>(b); 43 | b = ss<16, 0x0000ffff0000ffff>(b); 44 | b = ss< 8, 0x00ff00ff00ff00ff>(b); 45 | return b; 46 | } 47 | 48 | private: 49 | // shift + swap masked bits 50 | template 51 | static uint64_t ss(uint64_t b) { 52 | auto x = b & (M<>S | y< edge; 62 | // 8x8 tile core 63 | uint64_t core; 64 | 65 | tile() : id(), edge(), core() { } 66 | 67 | // Parse the tile 68 | tile(input_t &in) : edge(), core() { 69 | parse::skip(in, 5); // "Tile " 70 | id = parse::positive(in); 71 | parse::skip(in, 2); // ":\n" 72 | 73 | int m = 1; // current e-w bit 74 | 75 | auto row = [&]() { 76 | auto& [ n, e, s, w ] = edge; 77 | w |= m & -(*in.s & 1); parse::skip(in, 1); 78 | auto c = parse::octet(in, 7); 79 | e |= m & -(*in.s & 1); parse::skip(in, 2); 80 | m <<= 1; 81 | return c; 82 | }; 83 | 84 | auto& [ n, e, s, w ] = edge; 85 | 86 | n = row() << 1; 87 | for (int i = 0; i < 64; i += 8) { 88 | core |= uint64_t(row()) << i; 89 | } 90 | s = row() << 1; 91 | 92 | // Copy corners from e-w to n-s 93 | n |= (w & 1) | ((e << 9) & 0x200); 94 | s |= (w >> 9) | (e & 0x200); 95 | } 96 | 97 | // Get oriented eastern edge 98 | uint16_t east(int ori) { 99 | ori = 0x07432561 >> (ori * 4); 100 | auto e = edge[ori & 3]; 101 | return (ori & 4) ? xf::rev(e) : e; 102 | } 103 | 104 | // Get oriented southern edge 105 | uint16_t south(int ori) { 106 | auto e = edge[(ori ^ 2) & 3]; 107 | return (ori & 4) ? xf::rev(e) : e; 108 | } 109 | 110 | // Get oriented core 111 | uint64_t get_core(int ori) { 112 | auto c = core; 113 | int m = 0x9c281450U >> (ori * 4); 114 | if (m & 1) c = xf::diag(c); 115 | if (m & 2) c = xf::anti(c); 116 | if (m & 4) c = xf::ns(c); 117 | if (m & 8) c = xf::ew(c); 118 | return c; 119 | } 120 | }; 121 | 122 | // Tile index & orientation 123 | struct tile_ori { 124 | int idx, ori; 125 | tile_ori() : idx(), ori() { } 126 | tile_ori(int idx, int ori) : idx(idx), ori(ori) { } 127 | }; 128 | 129 | // Mapping of edges to tiles 130 | struct tile_index { 131 | std::array, 1024> I = { }; 132 | // For identifying unpaired edges 133 | std::bitset<1024> Parity; 134 | 135 | // Add a tile to the index 136 | void add(tile &t, int idx) { 137 | auto set = [&](int e, int ori) { 138 | I[e][1] = std::exchange(I[e][0], { idx, ori }); 139 | Parity.flip(e); 140 | }; 141 | int ori = 0; 142 | for (auto e : t.edge) { 143 | set(e, ori); 144 | set(xf::rev(e), 4|ori++); 145 | } 146 | } 147 | 148 | // Check if edge is part of the border 149 | bool parity(uint16_t e) const { 150 | return Parity.test(e); 151 | } 152 | 153 | // Find tile and orientation by north edge (exclude original tile) 154 | tile_ori match_north(int idx, uint16_t e) const { 155 | return I[e][I[e][0].idx == idx]; 156 | } 157 | 158 | // Find tile and orientation by west edge (exclude original tile) 159 | tile_ori match_west(int idx, uint16_t e) const { 160 | auto r = I[e][I[e][0].idx == idx]; 161 | r.ori = (0x25610743 >> (r.ori * 4)) & 7; 162 | return r; 163 | } 164 | }; 165 | 166 | struct grid { 167 | std::array,12> G; 168 | 169 | // Count the number of 1-bits 170 | int popcount() const { 171 | int count = 0; 172 | for (auto &row : G) { 173 | for (auto k : row) { 174 | count += _mm_popcnt_u64(k); 175 | } 176 | } 177 | return count; 178 | } 179 | 180 | // Reflect across the diagonal 181 | void diag_inplace() { 182 | for (int y = 0; y < 12; y++) { 183 | G[y][y] = xf::diag(G[y][y]); 184 | for (int x = y + 1; x < 12; x++) { 185 | G[y][x] = xf::diag(G[y][x]); 186 | std::swap(G[y][x], G[x][y]); 187 | G[y][x] = xf::diag(G[y][x]); 188 | } 189 | } 190 | } 191 | 192 | // Reflect east-west 193 | void ew_inplace() { 194 | for (auto &r : G) { 195 | for (int x = 0; x < 12; x += 2) { 196 | r[x] = xf::ew(r[x]); 197 | std::swap(r[x], r[11 - x]); 198 | r[x] = xf::ew(r[x]); 199 | } 200 | } 201 | } 202 | 203 | // Return the number of squares occupied by sea monsters, 204 | // assuming no overlap 205 | int find_monsters() const { 206 | auto u = west(), a = u & north(1), b = u & south(1); 207 | b = b & a.north(3); 208 | b = b & b.north(6); 209 | b = b & b.north(6); 210 | b = *this & b.north(2); 211 | b = b.west() & a.south(1); 212 | return b.popcount() * 15; 213 | }; 214 | 215 | private: 216 | // Shift west by 1 unit 217 | grid west() const { 218 | grid r; 219 | auto &H = r.G; 220 | constexpr uint64_t MASK = 0x0101010101010101; 221 | for (int y = 0; y < 12; y++) { 222 | for (int x = 0; x < 12; x++) { 223 | H[y][x] = (G[y][x] & ~MASK) >> 1; 224 | if (x) H[y][x-1] |= (G[y][x] & MASK) << 7; 225 | } 226 | } 227 | return r; 228 | } 229 | 230 | // Shift north by n units (max 7) 231 | grid north(int n) const { 232 | grid r; 233 | auto &H = r.G; 234 | int sr = 8 * n, sl = 64 - sr; 235 | for (int y = 0; y < 12; y++) { 236 | for (int x = 0; x < 12; x++) { 237 | H[y][x] = G[y][x] >> sr; 238 | if (y) H[y-1][x] |= G[y][x] << sl; 239 | } 240 | } 241 | return r; 242 | } 243 | 244 | // Shift south by n units (max 7) 245 | grid south(int n) const { 246 | grid r; 247 | auto &H = r.G; 248 | int sr = 8 * n, sl = 64 - sr; 249 | for (int y = 0; y < 12; y++) { 250 | for (int x = 0; x < 12; x++) { 251 | H[y][x] = G[y][x] << sr; 252 | if (y) H[y][x] |= G[y-1][x] >> sl; 253 | } 254 | } 255 | return r; 256 | } 257 | 258 | // Bitwise and 259 | grid operator& (const grid &o) const { 260 | grid r; 261 | auto &H = r.G; 262 | for (int y = 0; y < 12; y++) { 263 | for (int x = 0; x < 12; x++) { 264 | H[y][x] = G[y][x] & o.G[y][x]; 265 | } 266 | } 267 | return r; 268 | } 269 | 270 | }; 271 | 272 | } 273 | 274 | output_t day20(input_t in) { 275 | tile_index Tidx; 276 | 277 | std::vector T; 278 | T.reserve(144); 279 | 280 | while (in.len > 115) { 281 | T.emplace_back(in); 282 | Tidx.add(T.back(), T.size() - 1); 283 | } 284 | if (T.size() != 144) abort(); 285 | 286 | int64_t part1 = 1; 287 | 288 | tile_ori corner; 289 | 290 | // Solve Part 1 and also find any corner 291 | for (int idx = 0; idx < T.size(); idx++) { 292 | auto &t = T[idx]; 293 | int c = 0; 294 | for (auto e : t.edge) { 295 | c = (c << 1) | Tidx.parity(e); 296 | } 297 | if ((011110 >> c) & 1) { 298 | part1 *= t.id; 299 | corner = { idx, c }; 300 | } 301 | } 302 | 303 | // Orient the corner's borders northwest 304 | corner.ori = (010520 >> corner.ori) & 7; 305 | 306 | // Assemble the grid 307 | grid G; 308 | for (int y = 0; y < 12; y++) { 309 | auto t = corner; 310 | corner = Tidx.match_north(t.idx, T[t.idx].south(t.ori)); 311 | G.G[y][0] = T[t.idx].get_core(t.ori); 312 | for (int x = 1; x < 12; x++) { 313 | t = Tidx.match_west(t.idx, T[t.idx].east(t.ori)); 314 | G.G[y][x] = T[t.idx].get_core(t.ori); 315 | } 316 | } 317 | 318 | // Look for sea monsters in various orientations 319 | int part2 = 0; 320 | for (int i = 0; i < 4; i++) { 321 | if ((part2 = G.find_monsters())) break; 322 | G.ew_inplace(); 323 | if ((part2 = G.find_monsters())) break; 324 | G.diag_inplace(); 325 | } 326 | 327 | // Count the non-monster squares 328 | part2 = G.popcount() - part2; 329 | 330 | return { part1, part2 }; 331 | } 332 | -------------------------------------------------------------------------------- /src/day21.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | output_t day21(input_t in) { 4 | // Hash the allergen name to the range [0..8] 5 | auto allergen_index = [&](uint64_t w) { 6 | w = ((w >> 33) ^ (w >> 56)) & 60; 7 | return (0x57800412036 >> w) & 15; 8 | }; 9 | 10 | // Test 5-bit fields for nonzero 11 | auto nonzero5 = [](uint64_t x) { 12 | for (int i : { 1, 2, 1 }) x &= x >> i; 13 | return x & 0x10842108421; 14 | }; 15 | 16 | /* Hashtable of words mapped to: 17 | * - Number of times paired with each allergen 18 | * - Total number of appearances 19 | */ 20 | std::array Hw = { }, Ha = { }; 21 | 22 | auto update_word = [&](uint64_t w, uint64_t allergens) { 23 | int i = w % 1021; 24 | while (Hw[i] && Hw[i] != w) i++; 25 | Hw[i] = w; 26 | Ha[i] += allergens + (1LL << 45); 27 | }; 28 | 29 | int part1 = 0; 30 | 31 | // Parse the input 32 | uint64_t word = 0, allergens = 0, a_total = 0; 33 | std::vector V; 34 | for (int shift = 64; in.len; ) { 35 | auto c = *in.s; 36 | if (c == ' ') { 37 | V.push_back(word); 38 | word = 0, shift = 64; 39 | parse::skip(in, 1); 40 | } else if (c == '(') { 41 | parse::skip(in, 10); // "(contains " 42 | } else if (c == ',' || c == ')') { 43 | allergens |= 1LL << (allergen_index(word) * 5); 44 | word = 0, shift = 64; 45 | parse::skip(in, 2); 46 | 47 | if (c == ')') { 48 | part1 += V.size(); 49 | while (!V.empty()) { 50 | update_word(V.back(), allergens); 51 | V.pop_back(); 52 | } 53 | a_total += allergens; 54 | allergens = 0; 55 | } 56 | } else { 57 | word |= uint64_t(c) << (shift -= 7); 58 | parse::skip(in, 1); 59 | } 60 | } 61 | 62 | // Mask of valid allergens 63 | allergens = ~nonzero5(~a_total); 64 | 65 | // Find all words that completely match at least one allergen 66 | int n_match = 0; 67 | for (int i = 0; i < Hw.size(); i++) { 68 | if (!Ha[i]) continue; 69 | uint64_t match = allergens & nonzero5(~a_total ^ Ha[i]); 70 | if (!match) continue; 71 | Hw[n_match] = Hw[i]; 72 | Ha[n_match++] = match; 73 | part1 -= (Ha[i] >> 45); 74 | } 75 | 76 | // Find which specific ingredient matches which allergen 77 | std::array Ingredient = { }; 78 | while (n_match) for (int i = 0; i < n_match; i++) { 79 | Ha[i] &= allergens; 80 | if (_mm_popcnt_u64(Ha[i]) != 1) continue; 81 | allergens ^= Ha[i]; 82 | Ingredient[_mm_tzcnt_64(Ha[i]) / 5] = Hw[i]; 83 | Hw[i] = Hw[--n_match]; 84 | Ha[i] = Ha[n_match]; 85 | } 86 | 87 | // Convert the words to a list of strings 88 | std::string part2; 89 | for (auto s : Ingredient) { 90 | if (!s) continue; 91 | for (; s; s <<= 7) part2.push_back(s >> 57); 92 | part2.push_back(','); 93 | } 94 | if (!part2.empty()) part2.pop_back(); 95 | 96 | return { part1, part2 }; 97 | } 98 | -------------------------------------------------------------------------------- /src/day22.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | // Random BUZ hash values, one for each (card, player) pair 6 | static constexpr std::array Htable = { 7 | 0x2f6c30a572abb66a, 0x43181d2f6a7c6624, 0x394be292aa3ba2dc, 0x284cea25f597e52b, 8 | 0x14975a540dd4ff54, 0xe1aae11b42bc023a, 0xa6f4e6112a314500, 0x863e536e41c59c77, 9 | 0x4eaaa4a1a08da257, 0x7ae4b03953e3aff1, 0x237e795874c379bb, 0x849c2cf0efeb90ee, 10 | 0x36b06f4d45f84b53, 0x9489f8cce8b7ab81, 0xad1517dddd3d4584, 0xca933dd11c75ba65, 11 | 0x91da8cfe3faf2e0b, 0xb40d2f0f0accd202, 0x1b0d214e95c0dee5, 0x28bb75e43c271fed, 12 | 0xede31e378788cda8, 0xe698672d5e980f50, 0xeff9d41611cbb019, 0xa95ee9680f8fd6fb, 13 | 0x325b5edba311d0b5, 0x5ea0bcd55aab35b1, 0x675a1b19394f61a7, 0x5b2691819aee7c0e, 14 | 0x97cb5f117c3a3de6, 0xdb02bb8f04f413c9, 0xab3abaa6174ff768, 0x754e53e3c67a383b, 15 | 0xaeaa5c948b61f64c, 0xd81e7d25800ad3dc, 0x2a3e68f23e3c6858, 0x116c9f80f80d617a, 16 | 0xfb8601b39f1704c5, 0xf1c4e560c3a0a390, 0x8a37115a7f5f9ce1, 0xf16e2c62599d3810, 17 | 0xe63076b39751b857, 0xcfcf045428511e80, 0x21734139f1e43a6a, 0x30ee1abae2c4104b, 18 | 0xc3d13ac1233d2ba0, 0x63ded365ac705859, 0x462cc3e6ed7042bf, 0xc28450f7aba99c58, 19 | 0x1b31c4abb857259f, 0x8e19b11a548039bf, 0xf6839e65e9624a40, 0x6ed4e0c67a9cb686, 20 | 0x248368e255fc9f7e, 0xc5d28281339fd127, 0x5960fa04a0bae74e, 0x6728c82e704efc76, 21 | 0x485813ed286c1d70, 0x4e251815754ae5cd, 0xbdcfe14a7191864f, 0x11bc9e97bad6e7b0, 22 | 0x88772d6a11cfc4d6, 0x39e59294cd4cd1e4, 0x89549167158669af, 0x9ad1f18e00801d55, 23 | 0x663ad4f580b200a7, 0x4fcf4d42c3b96298, 0x91e755ff94d7dd1e, 0x1ef30d6e1c0be997, 24 | 0xcdb73a96ba06aa9b, 0xd555d85b16635d15, 0x54ea66890fa67731, 0xb9fc868f83a8fa1f, 25 | 0x7c8d49a5662764eb, 0x498be6569d9a09cb, 0x9533eed845458bfb, 0x8d41b72f2a265993, 26 | 0xfb07c8d2f3f01aec, 0xece949eae4b7aa5e, 0x80002f3965bc6e26, 0x6801407a4e192743, 27 | 0x7b29579ca72991ac, 0xd873bfa5fd567f15, 0x38420bd89e310693, 0x7037bf9f1647c7df, 28 | 0x7c978a5946b80880, 0x3665b20dff570e65, 0x74d72b0349c0c9ad, 0xf2c1866ae5ee8182, 29 | 0x5c608eef4a7771d1, 0x063ba7085f1fd12e, 0x6754e71dc1a13c3a, 0x13dd7510e260fe70, 30 | 0x34e06464cb58c2fb, 0xc7aa4debec66e22b, 0xa56a927afa78e6a4, 0x9bc1b2b8b55a4716, 31 | 0x0e352372fc1b176c, 0xd5bd86b8b002496c, 0x9215ed588250b5b8, 0x91bccdb69fe38880, 32 | }; 33 | 34 | struct solver { 35 | std::array, 2> V; // circular array 36 | std::array n{}; // hand lengths 37 | int base{}; // current base index in array 38 | uint8_t hi; 39 | 40 | // bitwise rotate left/right 41 | static uint64_t rol(uint64_t n, int r) { 42 | return (n << r) | (n >> (64 - r)); 43 | } 44 | 45 | static uint64_t ror(uint64_t n, int r) { 46 | return (n >> r) | (n << (64 - r)); 47 | } 48 | 49 | // Hash function 50 | static uint64_t H(int player, int card) { 51 | return Htable[(--card << 1) | player]; 52 | } 53 | 54 | // Get player's card at offset 55 | auto & D(int player, int offset) { 56 | return V[player][(base + offset) & 63]; 57 | } 58 | 59 | // Used for initializing player hands 60 | void push(int player, int card) { 61 | D(player, n[player]++) = card; 62 | } 63 | 64 | solver() : hi() { 65 | // high card doesn't matter for root level game 66 | } 67 | 68 | solver(const solver &s, int n0, int n1, uint8_t hi) 69 | : V(s.V), n({n0,n1}), base(s.base), hi(hi) 70 | { 71 | } 72 | 73 | // Compute score 74 | int score() { 75 | int s = 0, player = !n[0]; 76 | for (int i = 0, w = 50; i < n[player]; i++) { 77 | s += D(player, i) * w--; 78 | } 79 | return s; 80 | } 81 | 82 | // Part 1 83 | int solve1() { 84 | while (n[0] && n[1]) { 85 | int c0 = D(0,0), c1 = D(1,0); 86 | base++, n[0]--, n[1]--; 87 | if (c0 > c1) { 88 | D(0,n[0]++) = c0; 89 | D(0,n[0]++) = c1; 90 | } else { 91 | D(1,n[1]++) = c1; 92 | D(1,n[1]++) = c0; 93 | } 94 | } 95 | return !n[0]; 96 | } 97 | 98 | // Value of high card, but only if player 2 has it 99 | uint8_t high_card(int c0, int c1) { 100 | uint8_t hi = 0; 101 | for (int i = 0; i < c1; i++) hi = std::max(hi, D(1,i)); 102 | for (int i = 0; i < c0; i++) if (hi < D(0,i)) return 0; 103 | return hi; 104 | } 105 | 106 | int solve2() { 107 | uint64_t hash{}; 108 | for (int player = 0; player < 2; player++) { 109 | for (int i = 0; i < n[player]; i++) { 110 | hash ^= rol(H(player, D(player,i)), i); 111 | } 112 | } 113 | 114 | std::unordered_set seen; 115 | 116 | while (n[0] && n[1]) { 117 | // Check for cycle whenever player 2 is about 118 | // to play high card (this reduces expensive 119 | // lookups, and lowers chance of false positive) 120 | if (D(1,0) == hi && !seen.emplace(hash).second) { 121 | return 0; 122 | } 123 | 124 | // Draw cards, update game state hash 125 | int c0 = D(0,0), c1 = D(1,0); 126 | base++, n[0]--, n[1]--; 127 | hash = ror(hash ^ H(0, c0) ^ H(1, c1), 1); 128 | 129 | uint8_t new_hi; 130 | 131 | int winner = 0; 132 | 133 | if (c0 > n[0] || c1 > n[1]) { 134 | winner = c0 < c1; 135 | } else if ((new_hi = high_card(c0, c1)) == 0) { 136 | // Player 1 always wins with high card 137 | winner = 0; 138 | } else { 139 | solver s(*this, c0, c1, new_hi); 140 | winner = s.solve2(); 141 | } 142 | 143 | // Award cards to winner, update game state hash 144 | if (winner == 0) { 145 | hash ^= rol(H(0, c0), n[0]) ^ rol(H(0, c1), n[0] + 1); 146 | D(0,n[0]++) = c0; 147 | D(0,n[0]++) = c1; 148 | } else { 149 | hash ^= rol(H(1, c1), n[1]) ^ rol(H(1, c0), n[1] + 1); 150 | D(1,n[1]++) = c1; 151 | D(1,n[1]++) = c0; 152 | } 153 | } 154 | 155 | return !n[0]; 156 | } 157 | }; 158 | 159 | } 160 | 161 | output_t day22(input_t in) { 162 | solver S1; 163 | 164 | parse::skip(in, 10); 165 | for (int n = 0, player = 0; in.len; parse::skip(in, 1)) { 166 | if (*in.s != '\n') n = n * 10 + *in.s - '0'; 167 | else if (n) S1.push(player, n), n = 0; 168 | else player ^= 1, parse::skip(in, 10); 169 | } 170 | 171 | solver S2{S1}; 172 | 173 | S1.solve1(); 174 | auto part1 = S1.score(); 175 | 176 | S2.solve2(); 177 | auto part2 = S2.score(); 178 | 179 | return { part1, part2 }; 180 | } 181 | -------------------------------------------------------------------------------- /src/day23.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | template 4 | static int64_t solve(const std::vector &V, int *N, int len, int steps) { 5 | for (int i = 1; i < len; i++) N[i - 1] = i; 6 | for (int i = 1; i < V.size(); i++) N[V[i - 1]] = V[i]; 7 | 8 | if (V.size() == len) { 9 | N[V.back()] = V[0]; 10 | } else { 11 | N[V.back()] = V.size(); 12 | N[len - 1] = V[0]; 13 | } 14 | 15 | for (int s = 1, i = V[0]; s <= steps; s++) { 16 | auto j = i, a = N[i], b = N[a], c = N[b]; 17 | do { 18 | j = (j ? j : len) - 1; 19 | } while (j == a || j == b || j == c); 20 | i = N[i] = std::exchange(N[c], std::exchange(N[j], a)); 21 | } 22 | 23 | if (PART == 1) { 24 | int answer = 0, i = 0; 25 | while (--len) answer = 10 * answer + (i = N[i]) + 1; 26 | return answer; 27 | } else { 28 | int64_t a = N[0] + 1, b = N[N[0]] + 1; 29 | return a * b; 30 | } 31 | } 32 | 33 | output_t day23(input_t in) { 34 | std::vector V; 35 | 36 | for (; in.len; parse::skip(in, 1)) { 37 | uint8_t d = *in.s - '1'; 38 | if (d > '9') break; 39 | V.push_back(d); 40 | } 41 | 42 | constexpr int SIZE2 = 1000000; 43 | 44 | // Try to get 2MB pages if possible 45 | hugemem alloc(SIZE2 * sizeof(int)); 46 | auto N = (int *) alloc.ptr; 47 | 48 | auto part1 = solve<1>(V, N, V.size(), 100); 49 | auto part2 = solve<2>(V, N, SIZE2, 10000000); 50 | 51 | return { part1, part2 }; 52 | } 53 | -------------------------------------------------------------------------------- /src/day24.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | namespace { 4 | 5 | struct row { 6 | std::array<__m256i, 2> R; 7 | 8 | row() : R() { 9 | } 10 | 11 | void flip(int x) { 12 | auto p = (uint64_t *) &R[0]; 13 | p[x / 21] ^= 1LL << (3 * (x % 21)); 14 | } 15 | 16 | int count() const { 17 | auto p = (uint64_t *) &R[0]; 18 | int c = 0; 19 | for (int i = 0; i < 8; i++) { 20 | c += _mm_popcnt_u64(p[i]); 21 | } 22 | return c; 23 | } 24 | 25 | row operator + (const row &o) const { 26 | row r; 27 | r.R[0] = _mm256_add_epi64(R[0], o.R[0]); 28 | r.R[1] = _mm256_add_epi64(R[1], o.R[1]); 29 | return r; 30 | } 31 | 32 | row operator - (const row &o) const { 33 | row r; 34 | r.R[0] = _mm256_sub_epi64(R[0], o.R[0]); 35 | r.R[1] = _mm256_sub_epi64(R[1], o.R[1]); 36 | return r; 37 | } 38 | 39 | static auto com(__m256i v) { 40 | return _mm256_xor_si256(v, _mm256_set1_epi32(-1)); 41 | } 42 | 43 | void next(const row &C) { 44 | const auto M = _mm256_set1_epi64x(0x1249249249249249); 45 | for (int i = 0; i < 2; i++) { 46 | auto r = _mm256_or_si256(com(C.R[i]), R[i]); 47 | r = _mm256_and_si256(r, _mm256_and_si256( 48 | _mm256_srli_epi64(com(r), 1), 49 | _mm256_srli_epi64(r, 2))); 50 | R[i] = _mm256_and_si256(r, M); 51 | } 52 | } 53 | 54 | row& shr(bool) { 55 | auto l = _mm256_srli_epi64(R[0], 3), cl = _mm256_slli_epi64(R[0], 60); 56 | auto h = _mm256_srli_epi64(R[1], 3), ch = _mm256_slli_epi64(R[1], 60); 57 | auto ml = _mm256_permute2x128_si256(ch, cl, _MM_SHUFFLE(0,0,0,3)); 58 | auto mh = _mm256_permute2x128_si256(ch, ch, _MM_SHUFFLE(3,0,0,3)); 59 | R[0] = _mm256_or_si256(l, _mm256_alignr_epi8(ml, cl, 8)); 60 | R[1] = _mm256_or_si256(h, _mm256_alignr_epi8(mh, ch, 8)); 61 | return *this; 62 | } 63 | 64 | row& shl(bool) { 65 | auto l = _mm256_slli_epi64(R[0], 3), cl = _mm256_srli_epi64(R[0], 60); 66 | auto h = _mm256_slli_epi64(R[1], 3), ch = _mm256_srli_epi64(R[1], 60); 67 | auto ml = _mm256_permute2x128_si256(cl, cl, _MM_SHUFFLE(0,0,3,0)); 68 | auto mh = _mm256_permute2x128_si256(ch, cl, _MM_SHUFFLE(0,0,0,3)); 69 | R[0] = _mm256_or_si256(l, _mm256_alignr_epi8(cl, ml, 8)); 70 | R[1] = _mm256_or_si256(h, _mm256_alignr_epi8(ch, mh, 8)); 71 | return *this; 72 | } 73 | 74 | row shr() const { 75 | return row{*this}.shr(true); 76 | } 77 | 78 | row shl() const { 79 | return row{*this}.shl(true); 80 | } 81 | }; 82 | 83 | } 84 | 85 | output_t day24(input_t in) { 86 | int part1 = 0, part2 = 0; 87 | 88 | std::vector V(256), L(256), R(256); 89 | 90 | auto eq = [](__m256i v, char c) { 91 | auto eq = _mm256_cmpeq_epi8(v, _mm256_set1_epi8(c)); 92 | uint32_t r = _mm256_movemask_epi8(eq); 93 | return uint64_t(r); 94 | }; 95 | 96 | auto eq2 = [eq](__m256i l, __m256i h, char c) { 97 | return eq(l, c) | eq(h, c) << 32; 98 | }; 99 | 100 | int min_y = INT32_MAX, max_y = INT32_MIN; 101 | while (in.len) { 102 | __m256i l = _mm256_loadu_si256((__m256i *) in.s); 103 | __m256i h = _mm256_loadu_si256((__m256i *) in.s + 1); 104 | 105 | // Find positions of all n, s, w, e 106 | auto n = eq2(l, h, 'n'), s = eq2(l, h, 's'); 107 | auto w = eq2(l, h, 'w'), e = eq2(l, h, 'e'); 108 | 109 | // Find line length and mask the values 110 | auto mask = n | s | w | e; 111 | mask = mask ^ (mask + 1); 112 | parse::skip(in, _mm_tzcnt_64(~mask)); 113 | n &= mask, s &= mask, w &= mask, e &= mask; 114 | 115 | // Compute mapped hex coordinates 116 | int x = 84, y = 128; 117 | x += _mm_popcnt_u64(e & ~(n << 1)); 118 | x -= _mm_popcnt_u64(w & ~(s << 1)); 119 | y += _mm_popcnt_u64(s); 120 | y -= _mm_popcnt_u64(n); 121 | 122 | min_y = std::min(min_y, y); 123 | max_y = std::max(max_y, y); 124 | 125 | V[y].flip(x); 126 | } 127 | 128 | for (auto r : V) part1 += r.count(); 129 | 130 | for (int step = 1; step <= 100; step++) { 131 | min_y--, max_y++; 132 | for (int i = min_y; i <= max_y; i++) R[i] = V[i] + V[i].shr(); 133 | for (int i = min_y; i <= max_y; i++) L[i] = V[i] + V[i].shl(); 134 | for (int i = min_y; i <= max_y; i++) { 135 | V[i].next(R[i] + L[i] - V[i] + R[i+1] + L[i-1]); 136 | } 137 | } 138 | 139 | for (auto r : V) part2 += r.count(); 140 | 141 | return { part1, part2 }; 142 | } 143 | -------------------------------------------------------------------------------- /src/day25.cpp: -------------------------------------------------------------------------------- 1 | #include "advent2020.h" 2 | 3 | constexpr int64_t MOD = 20201227; 4 | 5 | static int modpow(int64_t n, int e) { 6 | int64_t p = 1; 7 | for (; e; e >>= 1) { 8 | if (e & 1) (p *= n) %= MOD; 9 | (n *= n) %= MOD; 10 | } 11 | return p; 12 | } 13 | 14 | static int dlog116099(int n) { 15 | constexpr int R = 341; 16 | 17 | auto H = [](int64_t n) { 18 | return ((1295*n) ^ (n>>8) ^ (n>>21)) % 2837; 19 | }; 20 | 21 | std::array Dk{}, Dv{}; 22 | for (int64_t br = 1, r = 0; r < R; r++) { 23 | int h = H(br); 24 | Dk[h] = br, Dv[h] = r; 25 | br = br * 19372176 % MOD; 26 | } 27 | 28 | int64_t y = modpow(n, 174); 29 | for (int i = 0; i < R; i++) { 30 | auto h = H(y); 31 | if (Dk[h] == y) { 32 | return i * R + Dv[h]; 33 | } 34 | y = y * 16585664 % MOD; 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | static int dlog29(int n) { 41 | const __m256i L = _mm256_set_epi64x( 42 | 0xffffff1a1c6615dc, 0x696ac3fae8143b9e, 43 | 0x8018b1cea1f4be4c, 0x757d308c0064b001); 44 | __m256i v = _mm256_set1_epi8(modpow(n, 696594) % 255); 45 | return _mm_tzcnt_32(_mm256_movemask_epi8(_mm256_cmpeq_epi8(L, v))); 46 | } 47 | 48 | static int dlog3(int n) { 49 | return modpow(n, 6733742) >> 5 & 3; 50 | } 51 | 52 | static int dlog2(int n) { 53 | return modpow(n, 10100613) != 1; 54 | } 55 | 56 | static int dlog(int n) { 57 | int64_t l = 0; 58 | l += 18227544LL * dlog116099(n); 59 | l += 18808038LL * dlog29(n); 60 | l += 13467484LL * dlog3(n); 61 | l += 10100613LL * dlog2(n); 62 | return l % (MOD - 1); 63 | } 64 | 65 | output_t day25(input_t in) { 66 | int k0 = 0, k1 = 0; 67 | 68 | for (; in.len; parse::skip(in, 1)) { 69 | if (*in.s == '\n') { 70 | std::swap(k0, k1); 71 | } else { 72 | k0 = 10 * k0 + *in.s - '0'; 73 | } 74 | } 75 | 76 | auto part1 = modpow(k0, dlog(k1)); 77 | 78 | return { part1, 0 }; 79 | } 80 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "advent2020.h" 4 | 5 | static constexpr int INPUT_PADDING = 64; 6 | 7 | static const std::vector advent2020 = { 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 <= advent2020.size(); day++) { 24 | auto &A = advent2020[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/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2020_MEMORY 2 | #define _ADVENT2020_MEMORY 3 | 4 | class hugemem { 5 | bool is_mmap; 6 | int sz; 7 | 8 | public: 9 | int64_t *ptr; 10 | 11 | hugemem(int _sz); 12 | ~hugemem(); 13 | }; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/numeric.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2020_NUMERIC 2 | #define _ADVENT2020_NUMERIC 3 | 4 | template 5 | static T fastmod(T n, T mod) { 6 | n -= mod & -(n >= mod); 7 | return n; 8 | } 9 | 10 | template 11 | static T gcdx(T a, T b, T &x, T &y) { 12 | x = 1, y = 0; 13 | T u = 0, v = 1, q; 14 | while (b) { 15 | q = a / b; 16 | x = std::exchange(u, x - q * u); 17 | y = std::exchange(v, y - q * v); 18 | a = std::exchange(b, a - q * b); 19 | } 20 | return a; 21 | } 22 | 23 | template 24 | static T modinv(T b, T mod) { 25 | T x, y; 26 | gcdx(b, mod, x, y); 27 | x += mod & -(x < 0); 28 | return x; 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/parse.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADVENT2020_PARSE_H 2 | #define _ADVENT2020_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 | // Fixed-width positive integer 27 | template 28 | static T positive(input_t &in, int len, bool strict) { 29 | T n = 0; 30 | if (in.len < len) return 0; 31 | for (; len--; in.s++, in.len--) { 32 | uint8_t d = *in.s - '0'; 33 | if (d > 9) return 0; 34 | n = 10 * n + d; 35 | } 36 | 37 | // In strict mode, reject any prefix of a longer number 38 | if (strict && in.len && *in.s >= '0' && *in.s <= '9') { 39 | return 0; 40 | } 41 | 42 | return n; 43 | } 44 | 45 | // Extract the high bits after shifting each of the next eight bytes left 46 | static inline int octet(input_t &in, int shift) { 47 | auto m = *reinterpret_cast<__m64 *>(in.s); 48 | parse::skip(in, 8); 49 | return _mm_movemask_pi8(_mm_slli_si64(m, shift)); // SSE 50 | } 51 | 52 | } 53 | 54 | #endif 55 | --------------------------------------------------------------------------------