Bitwise operations act on every bit independently, with no carries. That one fact powers per-bit counting, MSB-first greedy, XOR prefix tricks and bitmask states. Submask enumeration, SOS DP, transforms and the XOR basis each have their own notes in this chapter.
Core idioms
| Expression | Meaning |
|---|---|
x >> i & 1 | bit |
x | 1 << i, x & ~(1 << i), x ^ 1 << i | set, clear, toggle bit |
x & -x | lowest set bit (as a value): lowbit |
x & (x - 1) | clear the lowest set bit |
x | (x + 1) | set the lowest clear bit |
x & (x + 1) | clear the trailing ones |
~x & (x + 1) | lowest clear bit (as a value) |
x ^ (x + 1) | mask of the trailing ones plus the next bit |
x && !(x & (x - 1)) | power of two |
(1 << k) - 1 | low bits |
(a & b) == a | |
x ^ (x >> 1) | Gray code |
~(a & b), ~(a | b), ~(a ^ b) | NAND, NOR, XNOR |
x & ~((1 << k) - 1), (x + (1 << k) - 1) & ~((1 << k) - 1) | round down / up to a multiple of |
Every integer is a sum of distinct powers of two (); every bit operation below is just a statement about that expansion.
Use 1LL << i for , and 1ULL << 63 for the top bit. Shifting by the width is undefined behaviour. Right-shifting a negative signed integer is an arithmetic shift in practice (and guaranteed since C++20), so -7 >> 1 == -4, which is floor division.
Builtins
GCC builtin (ll suffix for 64-bit) | C++20 <bit> (unsigned only) | Result |
|---|---|---|
__builtin_popcount(x) | popcount(x) | number of set bits |
__builtin_ctz(x) | countr_zero(x) | trailing zeros (undefined for 0 with the builtin) |
__builtin_clz(x) | countl_zero(x) | leading zeros (undefined for 0 with the builtin) |
__lg(x), 31 - __builtin_clz(x) | bit_width(x) - 1 | |
__builtin_parity(x) | popcount(x) & 1 | parity |
__builtin_ffs(x) | index of the lowest set bit, 1-based (0 if ); __builtin_ffsll for 64-bit | |
__builtin_bswap32/64(x) | byte swap | |
bit_ceil(x), bit_floor(x) | next / previous power of two | |
rotl, rotr | rotations |
With #pragma GCC target("popcnt") these compile to single instructions; without it, __builtin_popcount calls a slower routine.
Arithmetic identities
Proof:
Binary addition, bit by bit, is two independent computations: the sum bit ignoring carries, and the carry generated. XOR gives exactly the carry-free sum at each position (; , matching ), and AND gives exactly the positions where a carry into the next bit is generated (only carries). Each such carry is worth at its own position (it lands one bit higher), so the total carry contribution is . Summing the carry-free part and the carry contribution reconstructs ordinary addition: . The second form follows from (every bit set in is either set in exactly one of , counted once by , or set in both, counted once by as and once by ), substituted into the first identity.
Consequences:
- .
- Given and : , which must be a non-negative integer with . Then the pairs number (minus the ones with a zero, if positives are required).
- In interactive problems, querying and (or ) gives , and three pairwise sums recover three numbers.
- , and .
- : XOR is subtraction (and addition) without borrows (carries).
- When guessing an inequality between bitwise and arithmetic expressions, brute-force all pairs below 64 first.
XOR facts
- XOR is addition in : associative, commutative, , every element is its own inverse. So “XOR of a range” is a prefix difference: .
- for .
- Unique element when all others appear twice: XOR everything. Two unique elements: XOR everything to get , split the numbers by any set bit of it, XOR each group. Element appearing once when others appear three times: count each bit mod 3.
- Subarrays with XOR : count pairs of prefixes with via a hash map.
- Tree paths: where is the root-to-vertex XOR (the LCA part cancels).
- Minimum XOR pair of a set is attained by two elements adjacent in sorted order. Maximum XOR pair needs a binary trie (Binary Trie).
- Maximum with : .
- Maximum subset XOR, -th subset XOR, is representable: XOR basis (XOR Basis).
- Sum of pairwise XORs / ANDs / ORs: count ones per bit, . Then the XOR sum is , the AND sum is , and the OR sum is .
- Nim: a position is losing for the player to move iff the XOR of the pile sizes is 0 (Combinatorial Games and Nim).
Per-bit independence and MSB greedy
Independence: for AND/OR/XOR expressions, solve each bit separately and add (count for bit ). This turns sums over pairs or subarrays of bitwise values into counting.
MSB-first greedy: , so securing a 1 at bit beats any combination of lower bits. Decide bits from the top and never revisit. Patterns:
- maximum AND of two elements: for from high to low, keep the candidate prefix
ans | 1 << bif at least two numbers contain it as a submask; - maximum AND of elements, maximum OR with constraints, minimum OR of a partition into segments: same “try to fix this bit, check feasibility” loop;
- find minimising : recurse on the top bit, splitting the set (or a trie) when both values appear;
- choose maximising : go through bits from high to low, staying tight with as in digit DP.
Iterating over bits and masks
for (ull m = x; m; m &= m - 1) { int i = __builtin_ctzll(m); /* each set bit i */ }
for (int s = m; ; s = (s - 1) & m) { /* each submask, decreasing */ if (!s) break; }
for (int s = m; s < 1 << n; s = (s + 1) | m) { /* each supermask */ }
// next mask with the same popcount (Gosper's hack): enumerate k-subsets in increasing order
unsigned nxt(unsigned v) { unsigned t = v | (v - 1); return (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1)); }Sets as masks: union |, intersection &, difference a & ~b, symmetric difference ^, complement within bits full ^ a, size popcount.
Other tricks
- Reverse bits of a 32-bit word: swap halves, then bytes, nibbles, pairs, single bits with masks (
0x55555555,0x33333333,0x0f0f0f0f, …), or a 256-entry table. - Popcount table for 16-bit chunks when computing many popcounts of 64-bit words without hardware support.
- Highest power of two dividing :
n & -n. :__builtin_ctzll(n). - Sign without branches:
(x > 0) - (x < 0). Absolute value:(x ^ (x >> 63)) - (x >> 63)for 64-bit. min/max without branches:y ^ ((x ^ y) & -(x < y)). - Swap via XOR:
a ^= b ^= a ^= bis undefined whenaandbalias; useswap. - Bit-parallel tricks: comparing 8 bytes at once, finding a zero byte with
(v - 0x0101..) & ~v & 0x8080.., SWAR popcount. These matter only in constant-factor battles;bitsetcovers most contest uses (Bitset), including the GCC extensions_Find_first/_Find_nextwhich iterate only the set bits of a sparse bitset in instead of .
Boolean algebra identities
The laws behind every simplification of an AND/OR/NOT expression (XOR-specific identities are in “XOR facts” above):
| Law | AND / OR form |
|---|---|
| Identity | |
| Null | |
| Idempotent | |
| Complement | |
| Double negation | |
| Distributive | , and the dual |
| Absorption | |
| De Morgan |
Common problems
| Problem | Approach |
|---|---|
| count pairs with | SOS over complements (SOS DP) |
| count pairs with | binary trie, walking bits of |
| subarrays with OR | two pointers with bit counts, or distinct ORs per right end |
| minimise | each bit independently: set bit of if most have it |
| construct distinct numbers with XOR | fill with small distinct numbers, fix the last two with high bits |
| XOR of all pairwise sums | per bit , count pairs whose sum has bit set: sort values mod , two pointers on two ranges |
| Gray code and Hamiltonian paths on the hypercube | Gray Code |
| tiling / frontier DP where the mask is a fill-pattern, not an item subset | broken-profile DP (Profile DP) |