Search code examples
c++bit-manipulationbitwise-operatorsbit

Determining cell value using bitwise operation with adjacent cell values


A r*c grid has only 0's ans 1's . In each iteration , if there is any adjacent cell (up,down,left,right) same to it, the value of the current cell will be flipped . Now , how to come up with a bitwise formula to do this . It can be done with a simple if condition , but I want to know the bitwise operation to do this so the whole operation can be done once per row .

I am talking about this problem . I saw a solution using this concept here . But I couldn't understand how this is used to do the determine the cell value by this XOR operations.

ans[i] ^= ((l ^ r) | (r ^ u) | (u ^ d)) | (~s[i] ^ l);
ans[i] &= prefix;

Any help would be appreciated :D


Solution

  • For the start, consider s[i], l, r, u, and d to be single bits, that is, boolean variables.

    • s[i] (abbreviated as s in this answer) is the old color of the cell to be updated.
    • l, r, u, and d are the colors of the adjacent cells left, right, above (up), and below (down) of the cell to be updated.
    • ans[i] (abbreviated as ans in this answer) is the new color of the cell after the update.
      We initialize ans = s and update it only if needed.

    Recall the rules from the game for a single cell C:

    • If all cells adjacent to C have the opposite color of C, then C retains its color.
    • Otherwise (if a cell adjacent to C has the same color as C), C changes its color.

    Are there various adjacent colors?

    For the first condition you can use a fail-fast approach. No matter the color of C, if the adjacent cells have various colors (some are 0 and some are 1) then C changes its color. To check whether the adjacent cells l, r, u, and d have various colors you only need three checks ✱:

    various_adjacent_colors = (l != r) || (r != u) || (u != d)
    

    In bit-wise notation this is
    various_adjacent_colors = (l ^ r) | (r ^ u) | (u ^ d)

    ✱ The "missing" checks like r != d are not necessary. Think about it the other way: If all three checks fail, then we know (l == r) && (r == u) && (u == d). In that case, from transitivity of == follows that (l == u), and (l == d), and (r == d). Therefore, all colors are the same.

    Fail-Fast for various adjacent colors

    If we find various adjacent colors, then we change s:

    if (various_adjacent_colors)
      ans = !s
    

    In bit-wise notation this is
    ans ^= various_adjacent_colors

    Are all colors equal?

    If we did not fail-fast, we know that all adjacent colors are equal to each other but not if they are equal to s. If s == all_adjacent_colors then we change s and if s != all_adjacent_colors then we retain s.

    if (!various_adjacent_colors && s == l) // l can be replaced by either r, u, or d
      ans = !s
    

    In bit-wise notation this is
    ans ^= ~various_adjacent_colors & ~(s ^ l) or
    ans ^= ~various_adjacent_colors & (~s ^ l)

    Putting everything together

    Now let's inline (and slightly simplify) all the bit-wise notations:

    vari = (l ^ r) | (r ^ u) | (u ^ d); ans ^= vari; ans ^= ~vari & (~s ^ l) is the same as
    vari = (l ^ r) | (r ^ u) | (u ^ d); ans ^= vari | (~s ^ l) is the same as
    ans ^= ((l ^ r) | (r ^ u) | (u ^ d)) | (~s ^ l)

    Seems familiar, right? :)

    From single bits to bit-vectors

    So far, we only considered single bits. The linked solution uses bit-vectors instead to simultaneously update all bits/cells in a row of the 2D game board. This approach only fails at the borders of the game board:

    • From r = s[i] << 1 the game board might end up bigger than it should be. ans[i] &= prefix fixes the size by masking overhanging bits.

    • At the top and bottom row the update does not work because u = s[i-1] and d = s[i+i] do not exist. The author updates these rows "manually" in a for loop.

    • The update for the leftmost and rightmost cell in each row might be wrong since r = s[i] << 1 and l = s[i] >> 1 shift in "adjacent" cells of color 0 which are not actually in the game. The author updates these cells "manually" in another for loop.

    By the way: A (better?) alternative to the mentioned "manual" border updates is to slightly enlarge the game board with an additional virtual row/column at each border. Before each game iteration, the virtual rows/columns are initialized such that they don't affect the update. Then the update of the actual game board is done as usual. The virtual rows/columns don't have to be stored, instead use ...

    // define once for the game
    bitset<N> maskMsb, maskLsb;
    maskMsb[m-1] = 1;
    maskLsb[0] = 1;
    // define for each row when updating the game board
    bitset<N> l = (s[i] >> 1) | (~s[i] & maskMsb);
    bitset<N> r = (s[i] << 1) | (~s[i] & maskLsb);
    bitset<N> u = i+1 <= n-1 ? s[i+1] : ~s[n-1];
    bitset<N> d = i-1 >= 0   ? s[i-1] : ~s[0];