A binary 8x8 matrix is 'unholy' if there are no isolated 1s. A 1 is isolated if there isn't another 1 in any of the 4 direct cardinal directions. Write a C program that counts the total number of unholy 8x8 matricies.To solve this question: - Use bit arithmetic and an unsigned long to represent the matrix. - Write a function is_unholy that tests if a matrix is unholy by bit-shifting and masking. Use a dynamic program to count the number of unholy matricies. - Recurse on the number of rows the matrix has. - Enumerate all 2^16 next two rows. This is enough to completely isolate the prior solutions from the next. Write out a plan for the program, and then implement the plan in C.
A binary 8x8 matrix is 'unholy' if there are no isolated 1s. A 1 is isolated if there isn't another 1 in any of the 4 direct cardinal directions. Write a C program that counts the total number of unholy 8x8 matricies.To solve this question: - Use bit arithmetic and an unsigned long to represent the matrix. - Write a function is_unholy that tests if a matrix is unholy by bit-shifting and masking. Use a dynamic program to count the number of unholy matricies. - Recurse on the number of rows the matrix has. - Enumerate all 2^16 next two rows. This is enough to completely isolate the prior solutions from the next. Write out a plan for the program, and then implement the plan in C.
We can “divide‐and‐conquer” the 8×8 matrix by “building” it row‐by–row. In our solution the idea is to “seal off” each row once we know its immediate upper and lower neighbours. (Remember that a 1 in the middle row is “happy” (i.e. not isolated) if it has at least one 1 in one of its four cardinal directions: left, right, above, or below.) When building the matrix we have not yet “fixed” the neighbour below the current row so we must wait one more row before we “finalise” that row. We do this by recycling the idea that if we know three consecutive rows (which we call “above”, “middle” and “below”) then we can check every 1 in the “middle” row: it must have at least one adjacent 1 in that row (to its left or right) or in the “above” or “below” rows (in the same column). (In the top or bottom row the “missing” neighbour is taken as a row of all 0’s.) One way to “build” the matrix is to decide on the first two rows (there are 2^16 possibilities) and immediately check that the top row is “happy” using an all‐zero “above” row. Then, for each new row that we add (ranging over 0…255 as an 8–bit number) we check that the row above (the “middle” of the recent 3–row block) is valid given its above and below neighbours. When we finally fill the eighth row we check the bottom row with a “below” row of all 0’s. (Because once three consecutive rows are chosen the “middle” one can no longer be affected by future decisions, we say that its fate is “sealed”.) To make things fast we use bit–arithmetic. We represent a row as an 8–bit number (0 to 255) and represent an 8×8 matrix as an unsigned long (64 bits). (In a later helper function is_unholy we show how to check a complete matrix by “bit shifting and masking”.) In our dynamic–programming solution the state is described by three items: • row – the next row index we have yet to choose (from 0 to 8) • prev – the row two rows up (our “above” row in the triple) • curr – the row immediately above the row we are going to choose (the “middle” row) Once we pick a candidate for the next row we use a helper check (see below) to ensure that the middle row is unisolated. (For the top and bottom rows we include a “dummy” row of 0’s as the missing neighbour.) We sum over all valid completions. Another helper function, is_unholy, is provided to test a complete 64–bit matrix. (It scans all 64 bits using bit shifts and masks and checks the four cardinal directions for every 1.) Below is the complete annotated C code that implements the plan: ----------------------------------------------------------- /* unholy_matrix.c We count the number of “unholy” 8x8 binary matrices. A matrix is unholy if no 1 appears isolated – that is, if whenever a cell is 1 it has at least one neighbouring 1 (directly left, right, above, or below). We use DP to construct the matrix rows from top to bottom. We represent a row as an unsigned char (8–bits) and a complete matrix as an unsigned long (64 bits). An entry in the matrix is found by shifting and masking. The dynamic programming recurses on the “next row index” along with the last two rows chosen. When three consecutive rows are available we check that the middle row is “okay”. We enumerate 2^8 possibilities for each new row. (We also include a helper function, is_unholy, that tests an entire matrix by directly scanning its entries with bit–shifts and masks.) */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> // Check that the "middle" row is valid (that is, no 1 is isolated) // given "above", "middle", and "below" rows. // For an isolated cell, note that a 1 is isolated if it has no neighbor in the same row // (left or right) and no neighbor in the row above and below (in the same column). // For border columns (0 and 7) we only check the valid neighbours and for missing rows (for top and bottom) // we supply a 0 row. int check_valid_row(unsigned char above, unsigned char middle, unsigned char below) { // For each bit (column) in the middle row: for (int col = 0; col < 8; col++) { // if the cell is not 1, nothing to check. if (!(middle & (1 << col))) continue; int has_neighbor = 0; // Check left neighbor (if col > 0) if (col > 0 && (middle & (1 << (col - 1)))) has_neighbor = 1; // Check right neighbor (if col < 7) if (!has_neighbor && col < 7 && (middle & (1 << (col + 1)))) has_neighbor = 1; // Check above (same col) in the "above" row if (!has_neighbor && (above & (1 << col))) has_neighbor = 1; // Check below, in the "below" row (same col) if (!has_neighbor && (below & (1 << col))) has_neighbor = 1; if (!has_neighbor) return 0; // isolated 1 found! } return 1; // middle row passes } // A recursive dynamic programming solution. // Parameters: // row: index of next row to fill (0-indexed, up to 8) // prev: the row two rows up (for checking above when the middle row becomes sealed) // curr: the row immediately above the next row (the "middle" row in the triple) // When row < 8, we will choose a candidate row (0..255) as the new row and check that // the "curr" row (the previous middle row) is valid given prev, curr, candidate. // When row == 8, we have filled all rows and now check the last row with a dummy row of 0. unsigned long long dp(int row, unsigned char prev, unsigned char curr, unsigned long long dpTable[9][256][256]) { // Base case: when we have filled all 8 rows (row==8), then "curr" (which is row7) needs to be validated // with a "below" row of all 0's. if (row == 8) { return check_valid_row(prev, curr, 0) ? 1ULL : 0ULL; } // Memoization check: dpTable[row][prev][curr] == -1 if not computed. if (dpTable[row][prev][curr] != (unsigned long long)(-1)) { return dpTable[row][prev][curr]; } unsigned long long ways = 0; // Try all possibilities for the next row (an 8-bit number, 0..255) for (int next = 0; next < 256; next++) { unsigned char nextRow = (unsigned char)next; // Now that we have a triple: (prev, curr, nextRow) we can "seal" the curr row. if (!check_valid_row(prev, curr, nextRow)) continue; ways += dp(row + 1, curr, nextRow, dpTable); } dpTable[row][prev][curr] = ways; return ways; } // A helper function: is_unholy // Given an unsigned long (64–bit) representing an 8×8 binary matrix, // check whether every 1 (cell) has at least one adjacent (up, down, left, right) 1. // Convention: row 0 occupies bits 0..7 (least significant) and row 7 is the highest 8 bits. int is_unholy(uint64_t matrix) { // Loop through rows and columns. for (int row = 0; row < 8; row++) { // Extract row bits unsigned char rbits = (matrix >> (8 * row)) & 0xFF; // Get the above row bits (or 0 if top row) unsigned char above = (row == 0) ? 0 : (matrix >> (8 * (row - 1))) & 0xFF; // Get the below row bits (or 0 if bottom row) unsigned char below = (row == 7) ? 0 : (matrix >> (8 * (row + 1))) & 0xFF; // Now check each column in the row: for (int col = 0; col < 8; col++) { if (!(rbits & (1 << col))) continue; // cell is 0, nothing to do. int ok = 0; // Check left in same row: if (col > 0 && (rbits & (1 << (col - 1)))) ok = 1; // Check right in same row: if (!ok && col < 7 && (rbits & (1 << (col + 1)))) ok = 1; // Check above (same column) if (!ok && (above & (1 << col))) ok = 1; // Check below (same column) if (!ok && (below & (1 << col))) ok = 1; if (!ok) return 0; // found an isolated 1. } } return 1; } // Main routine: int main(void) { // We'll use a top–down DP. // Allocate a DP table: dimensions dp[9][256][256]. We use 9 for rows 0..8. // We initialize all entries to (unsigned long long) -1 to denote "not computed". static unsigned long long dpTable[9][256][256]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 256; j++) { for (int k = 0; k < 256; k++) { dpTable[i][j][k] = (unsigned long long)(-1); } } } unsigned long long total = 0; // Now choose the first two rows. // For row0 (the top row) we pretend that the row above is all 0's. // For each possibility for row0 and row1, we immediately check that row0 is valid. for (int r0 = 0; r0 < 256; r0++) { unsigned char row0 = (unsigned char)r0; for (int r1 = 0; r1 < 256; r1++) { unsigned char row1 = (unsigned char)r1; if (!check_valid_row(0, row0, row1)) continue; // the top row failed because a cell in row0 is isolated. total += dp(2, row0, row1, dpTable); } } printf("Total number of unholy 8x8 matrices = %llu\n", total); // (Optional) Test the is_unholy function on one example. // Here we construct a matrix where every 1 is paired with another 1 (so it is unholy). // For instance, fill every row with the pattern 0xAA (10101010) so that every 1 is next to another 1. uint64_t sample = 0; for (int row = 0; row < 8; row++) { sample |= ((uint64_t)0xAA) << (8 * row); } printf("The sample matrix (0xAA pattern per row) is %sunholy.\n", is_unholy(sample) ? "" : "not "); return 0; } ----------------------------------------------------------- Explanation of the code: 1. Function check_valid_row(above, middle, below) scans the eight bits of the “middle” row. For every 1 it makes sure that at least one of its neighbours (left, right in the same row, or directly above or below) is set. (For boundary columns or missing neighbours we simply do not check or use a 0 value.) 2. The dp( ) function recursively “builds” the matrix by choosing a candidate for the next row (there are 256 possibilities). Once we have three consecutive rows we “seal off” the middle row by checking it with check_valid_row( ). The state is (row, prev, curr) so that we always have the two previous rows available (which is enough when we add the next row) so that the previous row’s fate is sealed. 3. The main routine first loops over all pairs of first two rows (r0 and r1), making sure that the top row (with an imaginary 0 row above) is valid. Then it invokes the DP for rows 2 to 7. 4. The helper function is_unholy( ) scans a complete 64–bit matrix. (This is implemented with bit–shifting and masking.) You can use this function to test any complete matrix representation. Compile the program (for example with gcc unholy_matrix.c -o unholy_matrix) and run. The program prints the total count of “unholy” 8×8 matrices. This implementation meets the requirements by: • Representing the matrix with bit–arithmetic (each row is an 8–bit number, an 8×8 matrix is a 64–bit unsigned long). • Testing “unholyness” both row–by–row in the dynamic program and by scanning an entire matrix in is_unholy( ). • Using recursion and enumerating 2^8 possibilities for the next row (thus 2^16 are “seen” when choosing two consecutive rows). This is the complete solution.
I extracted the following code from that output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | /* unholy_matrix.c We count the number of “unholy” 8x8 binary matrices. A matrix is unholy if no 1 appears isolated – that is, if whenever a cell is 1 it has at least one neighbouring 1 (directly left, right, above, or below). We use DP to construct the matrix rows from top to bottom. We represent a row as an unsigned char (8–bits) and a complete matrix as an unsigned long (64 bits). An entry in the matrix is found by shifting and masking. The dynamic programming recurses on the “next row index” along with the last two rows chosen. When three consecutive rows are available we check that the middle row is “okay”. We enumerate 2^8 possibilities for each new row. (We also include a helper function, is_unholy, that tests an entire matrix by directly scanning its entries with bit–shifts and masks.) */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> // Check that the "middle" row is valid (that is, no 1 is isolated) // given "above", "middle", and "below" rows. // For an isolated cell, note that a 1 is isolated if it has no neighbor in the same row // (left or right) and no neighbor in the row above and below (in the same column). // For border columns (0 and 7) we only check the valid neighbours and for missing rows (for top and bottom) // we supply a 0 row. int check_valid_row(unsigned char above, unsigned char middle, unsigned char below) { // For each bit (column) in the middle row: for (int col = 0; col < 8; col++) { // if the cell is not 1, nothing to check. if (!(middle & (1 << col))) continue; int has_neighbor = 0; // Check left neighbor (if col > 0) if (col > 0 && (middle & (1 << (col - 1)))) has_neighbor = 1; // Check right neighbor (if col < 7) if (!has_neighbor && col < 7 && (middle & (1 << (col + 1)))) has_neighbor = 1; // Check above (same col) in the "above" row if (!has_neighbor && (above & (1 << col))) has_neighbor = 1; // Check below, in the "below" row (same col) if (!has_neighbor && (below & (1 << col))) has_neighbor = 1; if (!has_neighbor) return 0; // isolated 1 found! } return 1; // middle row passes } // A recursive dynamic programming solution. // Parameters: // row: index of next row to fill (0-indexed, up to 8) // prev: the row two rows up (for checking above when the middle row becomes sealed) // curr: the row immediately above the next row (the "middle" row in the triple) // When row < 8, we will choose a candidate row (0..255) as the new row and check that // the "curr" row (the previous middle row) is valid given prev, curr, candidate. // When row == 8, we have filled all rows and now check the last row with a dummy row of 0. unsigned long long dp(int row, unsigned char prev, unsigned char curr, unsigned long long dpTable[9][256][256]) { // Base case: when we have filled all 8 rows (row==8), then "curr" (which is row7) needs to be validated // with a "below" row of all 0's. if (row == 8) { return check_valid_row(prev, curr, 0) ? 1ULL : 0ULL; } // Memoization check: dpTable[row][prev][curr] == -1 if not computed. if (dpTable[row][prev][curr] != (unsigned long long)(-1)) { return dpTable[row][prev][curr]; } unsigned long long ways = 0; // Try all possibilities for the next row (an 8-bit number, 0..255) for (int next = 0; next < 256; next++) { unsigned char nextRow = (unsigned char)next; // Now that we have a triple: (prev, curr, nextRow) we can "seal" the curr row. if (!check_valid_row(prev, curr, nextRow)) continue; ways += dp(row + 1, curr, nextRow, dpTable); } dpTable[row][prev][curr] = ways; return ways; } // A helper function: is_unholy // Given an unsigned long (64–bit) representing an 8×8 binary matrix, // check whether every 1 (cell) has at least one adjacent (up, down, left, right) 1. // Convention: row 0 occupies bits 0..7 (least significant) and row 7 is the highest 8 bits. int is_unholy(uint64_t matrix) { // Loop through rows and columns. for (int row = 0; row < 8; row++) { // Extract row bits unsigned char rbits = (matrix >> (8 * row)) & 0xFF; // Get the above row bits (or 0 if top row) unsigned char above = (row == 0) ? 0 : (matrix >> (8 * (row - 1))) & 0xFF; // Get the below row bits (or 0 if bottom row) unsigned char below = (row == 7) ? 0 : (matrix >> (8 * (row + 1))) & 0xFF; // Now check each column in the row: for (int col = 0; col < 8; col++) { if (!(rbits & (1 << col))) continue; // cell is 0, nothing to do. int ok = 0; // Check left in same row: if (col > 0 && (rbits & (1 << (col - 1)))) ok = 1; // Check right in same row: if (!ok && col < 7 && (rbits & (1 << (col + 1)))) ok = 1; // Check above (same column) if (!ok && (above & (1 << col))) ok = 1; // Check below (same column) if (!ok && (below & (1 << col))) ok = 1; if (!ok) return 0; // found an isolated 1. } } return 1; } // Main routine: int main(void) { // We'll use a top–down DP. // Allocate a DP table: dimensions dp[9][256][256]. We use 9 for rows 0..8. // We initialize all entries to (unsigned long long) -1 to denote "not computed". static unsigned long long dpTable[9][256][256]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 256; j++) { for (int k = 0; k < 256; k++) { dpTable[i][j][k] = (unsigned long long)(-1); } } } unsigned long long total = 0; // Now choose the first two rows. // For row0 (the top row) we pretend that the row above is all 0's. // For each possibility for row0 and row1, we immediately check that row0 is valid. for (int r0 = 0; r0 < 256; r0++) { unsigned char row0 = (unsigned char)r0; for (int r1 = 0; r1 < 256; r1++) { unsigned char row1 = (unsigned char)r1; if (!check_valid_row(0, row0, row1)) continue; // the top row failed because a cell in row0 is isolated. total += dp(2, row0, row1, dpTable); } } printf("Total number of unholy 8x8 matrices = %llu\n", total); // (Optional) Test the is_unholy function on one example. // Here we construct a matrix where every 1 is paired with another 1 (so it is unholy). // For instance, fill every row with the pattern 0xAA (10101010) so that every 1 is next to another 1. uint64_t sample = 0; for (int row = 0; row < 8; row++) { sample |= ((uint64_t)0xAA) << (8 * row); } printf("The sample matrix (0xAA pattern per row) is %sunholy.\n", is_unholy(sample) ? "" : "not "); return 0; } |
Running the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | /* unholy_matrix.c We count the number of “unholy” 8x8 binary matrices. A matrix is unholy if no 1 appears isolated – that is, if whenever a cell is 1 it has at least one neighbouring 1 (directly left, right, above, or below). We use DP to construct the matrix rows from top to bottom. We represent a row as an unsigned char (8–bits) and a complete matrix as an unsigned long (64 bits). An entry in the matrix is found by shifting and masking. The dynamic programming recurses on the “next row index” along with the last two rows chosen. When three consecutive rows are available we check that the middle row is “okay”. We enumerate 2^8 possibilities for each new row. (We also include a helper function, is_unholy, that tests an entire matrix by directly scanning its entries with bit–shifts and masks.) */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> // Check that the "middle" row is valid (that is, no 1 is isolated) // given "above", "middle", and "below" rows. // For an isolated cell, note that a 1 is isolated if it has no neighbor in the same row // (left or right) and no neighbor in the row above and below (in the same column). // For border columns (0 and 7) we only check the valid neighbours and for missing rows (for top and bottom) // we supply a 0 row. int check_valid_row(unsigned char above, unsigned char middle, unsigned char below) { // For each bit (column) in the middle row: for (int col = 0; col < 8; col++) { // if the cell is not 1, nothing to check. if (!(middle & (1 << col))) continue; int has_neighbor = 0; // Check left neighbor (if col > 0) if (col > 0 && (middle & (1 << (col - 1)))) has_neighbor = 1; // Check right neighbor (if col < 7) if (!has_neighbor && col < 7 && (middle & (1 << (col + 1)))) has_neighbor = 1; // Check above (same col) in the "above" row if (!has_neighbor && (above & (1 << col))) has_neighbor = 1; // Check below, in the "below" row (same col) if (!has_neighbor && (below & (1 << col))) has_neighbor = 1; if (!has_neighbor) return 0; // isolated 1 found! } return 1; // middle row passes } // A recursive dynamic programming solution. // Parameters: // row: index of next row to fill (0-indexed, up to 8) // prev: the row two rows up (for checking above when the middle row becomes sealed) // curr: the row immediately above the next row (the "middle" row in the triple) // When row < 8, we will choose a candidate row (0..255) as the new row and check that // the "curr" row (the previous middle row) is valid given prev, curr, candidate. // When row == 8, we have filled all rows and now check the last row with a dummy row of 0. unsigned long long dp(int row, unsigned char prev, unsigned char curr, unsigned long long dpTable[9][256][256]) { // Base case: when we have filled all 8 rows (row==8), then "curr" (which is row7) needs to be validated // with a "below" row of all 0's. if (row == 8) { return check_valid_row(prev, curr, 0) ? 1ULL : 0ULL; } // Memoization check: dpTable[row][prev][curr] == -1 if not computed. if (dpTable[row][prev][curr] != (unsigned long long)(-1)) { return dpTable[row][prev][curr]; } unsigned long long ways = 0; // Try all possibilities for the next row (an 8-bit number, 0..255) for (int next = 0; next < 256; next++) { unsigned char nextRow = (unsigned char)next; // Now that we have a triple: (prev, curr, nextRow) we can "seal" the curr row. if (!check_valid_row(prev, curr, nextRow)) continue; ways += dp(row + 1, curr, nextRow, dpTable); } dpTable[row][prev][curr] = ways; return ways; } // A helper function: is_unholy // Given an unsigned long (64–bit) representing an 8×8 binary matrix, // check whether every 1 (cell) has at least one adjacent (up, down, left, right) 1. // Convention: row 0 occupies bits 0..7 (least significant) and row 7 is the highest 8 bits. int is_unholy(uint64_t matrix) { // Loop through rows and columns. for (int row = 0; row < 8; row++) { // Extract row bits unsigned char rbits = (matrix >> (8 * row)) & 0xFF; // Get the above row bits (or 0 if top row) unsigned char above = (row == 0) ? 0 : (matrix >> (8 * (row - 1))) & 0xFF; // Get the below row bits (or 0 if bottom row) unsigned char below = (row == 7) ? 0 : (matrix >> (8 * (row + 1))) & 0xFF; // Now check each column in the row: for (int col = 0; col < 8; col++) { if (!(rbits & (1 << col))) continue; // cell is 0, nothing to do. int ok = 0; // Check left in same row: if (col > 0 && (rbits & (1 << (col - 1)))) ok = 1; // Check right in same row: if (!ok && col < 7 && (rbits & (1 << (col + 1)))) ok = 1; // Check above (same column) if (!ok && (above & (1 << col))) ok = 1; // Check below (same column) if (!ok && (below & (1 << col))) ok = 1; if (!ok) return 0; // found an isolated 1. } } return 1; } // Main routine: int main(void) { // We'll use a top–down DP. // Allocate a DP table: dimensions dp[9][256][256]. We use 9 for rows 0..8. // We initialize all entries to (unsigned long long) -1 to denote "not computed". static unsigned long long dpTable[9][256][256]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 256; j++) { for (int k = 0; k < 256; k++) { dpTable[i][j][k] = (unsigned long long)(-1); } } } unsigned long long total = 0; // Now choose the first two rows. // For row0 (the top row) we pretend that the row above is all 0's. // For each possibility for row0 and row1, we immediately check that row0 is valid. for (int r0 = 0; r0 < 256; r0++) { unsigned char row0 = (unsigned char)r0; for (int r1 = 0; r1 < 256; r1++) { unsigned char row1 = (unsigned char)r1; if (!check_valid_row(0, row0, row1)) continue; // the top row failed because a cell in row0 is isolated. total += dp(2, row0, row1, dpTable); } } printf("Total number of unholy 8x8 matrices = %llu\n", total); // (Optional) Test the is_unholy function on one example. // Here we construct a matrix where every 1 is paired with another 1 (so it is unholy). // For instance, fill every row with the pattern 0xAA (10101010) so that every 1 is next to another 1. uint64_t sample = 0; for (int row = 0; row < 8; row++) { sample |= ((uint64_t)0xAA) << (8 * row); } printf("The sample matrix (0xAA pattern per row) is %sunholy.\n", is_unholy(sample) ? "" : "not "); return 0; } |
Total number of unholy 8x8 matrices = 1121231537486377866
The sample matrix (0xAA pattern per row) is unholy.
Testing if the previous output contains the string 1121231537486377866
: True
A binary 8x8 matrix is 'unholy' if there are no isolated 1s. A 1 is isolated if there isn't another 1 in any of the 4 direct cardinal directions. Write a C program that counts the total number of unholy 8x8 matricies.To solve this question: - Use bit arithmetic and an unsigned long to represent the matrix. - Write a function is_unholy that tests if a matrix is unholy by bit-shifting and masking. Use a dynamic program to count the number of unholy matricies. - Recurse on the number of rows the matrix has. - Enumerate all 2^16 next two rows. This is enough to completely isolate the prior solutions from the next. Write out a plan for the program, and then implement the plan in C.
A binary 8x8 matrix is 'unholy' if there are no isolated 1s. A 1 is isolated if there isn't another 1 in any of the 4 direct cardinal directions. Write a C program that counts the total number of unholy 8x8 matricies.To solve this question: - Use bit arithmetic and an unsigned long to represent the matrix. - Write a function is_unholy that tests if a matrix is unholy by bit-shifting and masking. Use a dynamic program to count the number of unholy matricies. - Recurse on the number of rows the matrix has. - Enumerate all 2^16 next two rows. This is enough to completely isolate the prior solutions from the next. Write out a plan for the program, and then implement the plan in C.
We can “solve” the problem by “sweeping” the 8×8 matrix row‐by‐row and “locally” checking the unholiness condition. (A 1 is not isolated if at least one of its 4‐neighbors is also a 1.) One useful observation is that a 1 in row i is “resolved” (that is, we can decide if it is isolated or not) once we know its left/right neighbors in row i and the vertically adjacent cells – the cell immediately above (row i–1) and immediately below (row i+1). In other words, when “placing” rows we need to remember two consecutive rows so that the “middle” row’s ones can be checked with help from its above and below rows. (You can think of it as “2‑row buffering”.) Below is one plan for the program and then a complete C implementation. ────────────────────────── Program Plan 1. Represent each row as an 8‐bit mask (an unsigned char). A whole matrix is then 8 rows. (We use an unsigned long for the full matrix if you like but for our dynamic program a row–by–row decomposition is easiest.) 2. Write a helper function is_valid(above, cur, below) that tests the (unholiness) condition for one row (call it “cur”). For every bit (cell) j that is 1 in cur, at least one of the following must hold: • j–1 in cur is 1 (if j>0) • j+1 in cur is 1 (if j<7) • The cell in the same column in the above row is 1 • The cell in the same column in the below row is 1 If none of these hold the 1 is “isolated” so return false. 3. Use a dynamic–programming recursion that “sweeps” the five rows at a time using a 2‑row state. a. Define dp(row, prev, cur) to be the number of ways to “complete” the matrix (i.e. fill rows row, row+1,…,7) given that the last two rows placed are prev (row–1) and cur (row). Because the validity of a row is determined by its neighbors, you will “finalize” row–1 as soon as you know its “above” (which is in a previous dp state) and “below” (the current row). b. In dp(row, prev, cur) if row > 0 the row “prev” has already got its above (from the previous state) and below (cur) rows; before recursing further check that it is “valid” (that is, no 1 is left isolated). c. Now, try every possibility for the next row “nxt” (from 0 to 255). For each, if cur is valid (with below = nxt) then call dp(row+1, cur, nxt). d. Use memoization keyed by the current row index (from 1 to 8) and the two masks (prev and cur). Notice that the final row of the matrix (row 7) is “completed” when you call dp(8, row7, 0) (we pretend the “virtual row‐8” is all 0’s). 4. In main(), we must “start” the recursion. For row 0, assume a virtual row –1 of all 0’s. Then, for every possible first row (mask) call dp(1, 0, row0) and sum the results. 5. Finally, report the total count. You can “see” the use of bit arithmetic in two ways: • The “is_valid(above, cur, below)” function uses bit–mask shifts to check the left/right neighbors. • The DP iterates through the next row masks from 0 to 255 (that is, 2^8 possibilities) but by “pairing” rows we are effectively iterating 2^16 possibilities (the “current” row and the “next” row) in every DP transition. ────────────────────────── C Code Implementation Below is one complete C program following the above plan. (Compile with –std=c99 or later.) -------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <string.h> // We will use unsigned long long for the DP counts. typedef unsigned long long ull; // Memoization: dimensions: row index from 0 to 8 (9 values) and each of prev, cur are 0..255. #define NUM_ROWS 9 #define MASKS 256 // Use a global DP array. We use -1 (all bits 1 in a ull) to mark not computed. ull dpCache[NUM_ROWS][MASKS][MASKS]; // Helper: is_valid(above, cur, below) // For every set bit in 'cur', check if at least one neighbor (left/right in cur, or cell in same column in above or below) is set. int is_valid(unsigned char above, unsigned char cur, unsigned char below) { // For each column j from 0 to 7 where cur has a 1, check the neighbors. for (int j = 0; j < 8; j++) { if (cur & (1 << j)) { int hasNeighbor = 0; // Left neighbor in cur (if exists) if (j > 0 && (cur & (1 << (j - 1)))) hasNeighbor = 1; // Right neighbor in cur (if exists) if (j < 7 && (cur & (1 << (j + 1)))) hasNeighbor = 1; // Above neighbor in 'above' if (above & (1 << j)) hasNeighbor = 1; // Below neighbor in 'below' if (below & (1 << j)) hasNeighbor = 1; if (!hasNeighbor) return 0; // isolated 1 found } } return 1; } // dp(row, prev, cur): // row: next row index to be filled (0-indexed). When row==8, we have filled rows 0..7. // 'prev' is the row at index row-1 (already “completed”) // 'cur' is the row at index row (not yet final because its below neighbor is not known). ull dp (int row, unsigned char prev, unsigned char cur) { // Check the memo cache ull *mem = &dpCache[row][prev][cur]; if (*mem != (ull)-1) return *mem; ull total = 0; // When row == 8, we have placed row 7 as 'cur'. We then must “finalize” row 7 (the lower row) // by assuming its below neighbor is 0. if (row == 8) { if (is_valid(prev, cur, 0)) total = 1; else total = 0; *mem = total; return total; } // Before moving forward, row (row-1) now has above= 'prev' from the previous step // and below = 'cur'; so if row > 0 we know row-1 completely. if (row > 0) { if (!is_valid( /* above */ 0, /* cur */ prev, /* below */ cur)) { // However, note that in our dp the "above" row for row-1 has already been taken care of in a previous call, // so we do not re-check the left neighbor above row. In our scheme the function call 'dp(row, prev, cur)' // means that 'prev' (the row r-1) was not finalized when r-1’s "above" was not available. // (Thus, we check only when the state is created.) // In our scheme we check the validity in the transition below. // (The following test is done in the transition loop.) // So, we do nothing here. ; } } // Now, we must choose the next row (call it 'nxt') for row index 'row'. // At this point, row-1 has been “completed” in the previous transition. // We want to check the validity of the current row 'cur' when paired with neighbor 'prev' (above) // and 'nxt' (below). So we try each possibility for nxt. for (int nxt = 0; nxt < 256; nxt++) { if (is_valid(prev, cur, (unsigned char)nxt)) total += dp(row + 1, cur, (unsigned char)nxt); } *mem = total; return total; } int main(void) { // Initialize the DP cache with a special value; note that (ull)-1 is our “uncomputed” marker. for (int r = 0; r < NUM_ROWS; r++) { for (int i = 0; i < MASKS; i++) { for (int j = 0; j < MASKS; j++) { dpCache[r][i][j] = (ull)-1; } } } ull totalCount = 0; // For row 0, assume a virtual row -1 of all 0's. // We choose any candidate for row 0 from 0 to 255; its validity will be checked when we choose row 1. for (int row0 = 0; row0 < 256; row0++) { totalCount += dp(1, 0, (unsigned char)row0); } printf("Total number of unholy 8x8 matricies = %llu\n", totalCount); return 0; } -------------------------------------------------- ────────────────────────── Explanation • We use bit–shifts (for example, (1<<j)) and bit–masking to quickly check if the left neighbor (j–1) or right neighbor (j+1) of a given bit is set. • In function is_valid(above, cur, below) we scan over the bits in the current row “cur.” If for some position j the bit is set and none of the available neighbors (left/right in the same row, above in row “above” and below in row “below”) is set, then the row fails the unholiness test. • The dp() function “moves” row–by–row. Given that the previous two rows (prev, cur) are placed, we enumerate all 256 possibilities for the next row “nxt” (thus iterating over 2^8 possibilities) and check if the “middle” row (here cur) is valid. (Over the whole calculation roughly a 2^16 search is done in each transition.) • When we reach row==8 we finalize the check for the last real row (row 7), assuming a virtual all–zero row below. • The main() routine “starts” the recursion assuming the virtual row before row 0 is 0. This is one working solution in C that uses bit arithmetic and a DP recursion (with a two–row state) to count the total number of unholy 8×8 matricies. Compile and run the program to obtain the answer. Enjoy!
I extracted the following code from that output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #include <stdio.h> #include <stdlib.h> #include <string.h> // We will use unsigned long long for the DP counts. typedef unsigned long long ull; // Memoization: dimensions: row index from 0 to 8 (9 values) and each of prev, cur are 0..255. #define NUM_ROWS 9 #define MASKS 256 // Use a global DP array. We use -1 (all bits 1 in a ull) to mark not computed. ull dpCache[NUM_ROWS][MASKS][MASKS]; // Helper: is_valid(above, cur, below) // For every set bit in 'cur', check if at least one neighbor (left/right in cur, or cell in same column in above or below) is set. int is_valid(unsigned char above, unsigned char cur, unsigned char below) { // For each column j from 0 to 7 where cur has a 1, check the neighbors. for (int j = 0; j < 8; j++) { if (cur & (1 << j)) { int hasNeighbor = 0; // Left neighbor in cur (if exists) if (j > 0 && (cur & (1 << (j - 1)))) hasNeighbor = 1; // Right neighbor in cur (if exists) if (j < 7 && (cur & (1 << (j + 1)))) hasNeighbor = 1; // Above neighbor in 'above' if (above & (1 << j)) hasNeighbor = 1; // Below neighbor in 'below' if (below & (1 << j)) hasNeighbor = 1; if (!hasNeighbor) return 0; // isolated 1 found } } return 1; } // dp(row, prev, cur): // row: next row index to be filled (0-indexed). When row==8, we have filled rows 0..7. // 'prev' is the row at index row-1 (already “completed”) // 'cur' is the row at index row (not yet final because its below neighbor is not known). ull dp (int row, unsigned char prev, unsigned char cur) { // Check the memo cache ull *mem = &dpCache[row][prev][cur]; if (*mem != (ull)-1) return *mem; ull total = 0; // When row == 8, we have placed row 7 as 'cur'. We then must “finalize” row 7 (the lower row) // by assuming its below neighbor is 0. if (row == 8) { if (is_valid(prev, cur, 0)) total = 1; else total = 0; *mem = total; return total; } // Before moving forward, row (row-1) now has above= 'prev' from the previous step // and below = 'cur'; so if row > 0 we know row-1 completely. if (row > 0) { if (!is_valid( /* above */ 0, /* cur */ prev, /* below */ cur)) { // However, note that in our dp the "above" row for row-1 has already been taken care of in a previous call, // so we do not re-check the left neighbor above row. In our scheme the function call 'dp(row, prev, cur)' // means that 'prev' (the row r-1) was not finalized when r-1’s "above" was not available. // (Thus, we check only when the state is created.) // In our scheme we check the validity in the transition below. // (The following test is done in the transition loop.) // So, we do nothing here. ; } } // Now, we must choose the next row (call it 'nxt') for row index 'row'. // At this point, row-1 has been “completed” in the previous transition. // We want to check the validity of the current row 'cur' when paired with neighbor 'prev' (above) // and 'nxt' (below). So we try each possibility for nxt. for (int nxt = 0; nxt < 256; nxt++) { if (is_valid(prev, cur, (unsigned char)nxt)) total += dp(row + 1, cur, (unsigned char)nxt); } *mem = total; return total; } int main(void) { // Initialize the DP cache with a special value; note that (ull)-1 is our “uncomputed” marker. for (int r = 0; r < NUM_ROWS; r++) { for (int i = 0; i < MASKS; i++) { for (int j = 0; j < MASKS; j++) { dpCache[r][i][j] = (ull)-1; } } } ull totalCount = 0; // For row 0, assume a virtual row -1 of all 0's. // We choose any candidate for row 0 from 0 to 255; its validity will be checked when we choose row 1. for (int row0 = 0; row0 < 256; row0++) { totalCount += dp(1, 0, (unsigned char)row0); } printf("Total number of unholy 8x8 matricies = %llu\n", totalCount); return 0; } |
Running the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | #include <stdio.h> #include <stdlib.h> #include <string.h> // We will use unsigned long long for the DP counts. typedef unsigned long long ull; // Memoization: dimensions: row index from 0 to 8 (9 values) and each of prev, cur are 0..255. #define NUM_ROWS 9 #define MASKS 256 // Use a global DP array. We use -1 (all bits 1 in a ull) to mark not computed. ull dpCache[NUM_ROWS][MASKS][MASKS]; // Helper: is_valid(above, cur, below) // For every set bit in 'cur', check if at least one neighbor (left/right in cur, or cell in same column in above or below) is set. int is_valid(unsigned char above, unsigned char cur, unsigned char below) { // For each column j from 0 to 7 where cur has a 1, check the neighbors. for (int j = 0; j < 8; j++) { if (cur & (1 << j)) { int hasNeighbor = 0; // Left neighbor in cur (if exists) if (j > 0 && (cur & (1 << (j - 1)))) hasNeighbor = 1; // Right neighbor in cur (if exists) if (j < 7 && (cur & (1 << (j + 1)))) hasNeighbor = 1; // Above neighbor in 'above' if (above & (1 << j)) hasNeighbor = 1; // Below neighbor in 'below' if (below & (1 << j)) hasNeighbor = 1; if (!hasNeighbor) return 0; // isolated 1 found } } return 1; } // dp(row, prev, cur): // row: next row index to be filled (0-indexed). When row==8, we have filled rows 0..7. // 'prev' is the row at index row-1 (already “completed”) // 'cur' is the row at index row (not yet final because its below neighbor is not known). ull dp (int row, unsigned char prev, unsigned char cur) { // Check the memo cache ull *mem = &dpCache[row][prev][cur]; if (*mem != (ull)-1) return *mem; ull total = 0; // When row == 8, we have placed row 7 as 'cur'. We then must “finalize” row 7 (the lower row) // by assuming its below neighbor is 0. if (row == 8) { if (is_valid(prev, cur, 0)) total = 1; else total = 0; *mem = total; return total; } // Before moving forward, row (row-1) now has above= 'prev' from the previous step // and below = 'cur'; so if row > 0 we know row-1 completely. if (row > 0) { if (!is_valid( /* above */ 0, /* cur */ prev, /* below */ cur)) { // However, note that in our dp the "above" row for row-1 has already been taken care of in a previous call, // so we do not re-check the left neighbor above row. In our scheme the function call 'dp(row, prev, cur)' // means that 'prev' (the row r-1) was not finalized when r-1’s "above" was not available. // (Thus, we check only when the state is created.) // In our scheme we check the validity in the transition below. // (The following test is done in the transition loop.) // So, we do nothing here. ; } } // Now, we must choose the next row (call it 'nxt') for row index 'row'. // At this point, row-1 has been “completed” in the previous transition. // We want to check the validity of the current row 'cur' when paired with neighbor 'prev' (above) // and 'nxt' (below). So we try each possibility for nxt. for (int nxt = 0; nxt < 256; nxt++) { if (is_valid(prev, cur, (unsigned char)nxt)) total += dp(row + 1, cur, (unsigned char)nxt); } *mem = total; return total; } int main(void) { // Initialize the DP cache with a special value; note that (ull)-1 is our “uncomputed” marker. for (int r = 0; r < NUM_ROWS; r++) { for (int i = 0; i < MASKS; i++) { for (int j = 0; j < MASKS; j++) { dpCache[r][i][j] = (ull)-1; } } } ull totalCount = 0; // For row 0, assume a virtual row -1 of all 0's. // We choose any candidate for row 0 from 0 to 255; its validity will be checked when we choose row 1. for (int row0 = 0; row0 < 256; row0++) { totalCount += dp(1, 0, (unsigned char)row0); } printf("Total number of unholy 8x8 matricies = %llu\n", totalCount); return 0; } |
Total number of unholy 8x8 matricies = 1121231537486377866
Testing if the previous output contains the string 1121231537486377866
: True