[Basic operations] Bit operations: binary state compression, pairwise transformation, lowbit operation

1. Binary state compression

Binary state compression refers to compressing a file with a length of

m

m

The bool array of m uses a

m

m

A method of representing and storing m-bit binary integers.

The following bit operations can be used to access the corresponding subscript elements in the original bool array.

Operation Operation
Get the integer

n

n

n in binary representation

k

k

k bits

(n >> k) & amp; 1
Get the integer

n

n

n in binary representation

0

0

0 ~

k

?

1

k-1

k?1 bit

n & amp; ((1 << k) - 1)
Put the integer

n

n

n in binary representation

k

k

K bit negation

n ^ (1 << k)
For integers

n

n

n in binary representation

k

k

k-bit assignment 1

n | (1 << k)
pair of integers

n

n

n in binary representation

k

k

k bit assignment 0

n & amp; (~(1 << k))

This method is simple to operate and saves program running time and space. when

m

m

When m is not too large, you can directly use an integer type to store it. when

m

m

When m is large, you can use several integer types (int arrays), or you can directly use the bitset implementation provided by C++ STL.

2. Pairwise transformation

For non-negative integers

n

n

n:

  • when

    n

    n

    When n is an even number, n ^ 1 is equal to

    n

    +

    1

    n+1

    n+1

  • when

    n

    n

    When n is an even number, n ^ 1 is equal to

    n

    ?

    1

    n-1

    n?1

Therefore, “0 and 1” “2 and 3” “4 and 5” … operations on ^ 1 constitute “pairwise transformations”.

This property is often used to store edge sets in graph theory adjacency lists. In a graph with undirected edges (bidirectional edges), store a pair of forward and reverse edges in the adjacency list array.

n

n

n and

n

+

1

n+1

n + 1 position (where

n

n

n is an even number), you can obtain the current edge through the operation of ^ 1

(

x

,

y

)

(x, y)

(x,y) opposite side

(

y

,

x

)

(y,x)

The storage location of (y,x).

3. Lowbit operation

lowbit(n) is defined as a non-negative integer

n

n

n is a numerical value composed of “the lowest bit 1 and all following 0s” in binary representation. Such as

n

=

10

n=10

The binary representation of n=10 is

(

1010

)

2

(1010)_2

(1010)2?, then

l

o

w

b

i

t

(

n

)

=

2

=

(

10

)

2

lowbit(n) = 2 = (10)_2

lowbit(n)=2=(10)2?.

Derivation

l

o

w

b

i

t

(

n

)

lowbit(n)

The formula for lowbit(n).

set up

n

>

0

n > 0

n>0,

n

n

nth

k

k

The k bit is 1, and the

0

0

0 ~

k

?

1

k-1

The k?1 bits are all 0.

In order to implement lowbit operations, first

n

n

n is negated, at this time the

k

k

k becomes 0, and the

0

0

0 ~

k

?

1

k-1

The k?1 bits are all 1. Order again

n

=

n

+

1

n = n + 1

n=n + 1, at this time because of carry, the

k

k

k is changed to 1, the 0th ~

k

?

1

k-1

The k?1 bits are all 0.

After taking the inverse and adding 1 operation above,

n

n

nth

k

+

1

k+1

k + 1 bit to the highest bit is exactly the opposite of the original, so n & amp; (~n + 1) only has the

k

k

The k bits are 1, and the remaining bits are 0. In two’s complement representation, ~n = -1 - n, therefore

?lowbit(n) = n & amp; (~n + 1) = n & amp; (-n)

lowbit operation combined with Hash can find all the bits that are 1 in the binary representation of the integer, and the time spent is the same as the number of 1’s. To achieve this goal, just continue to assign n to n – lowbit(n) until

n

=

0

n=0

n=0.

For example, n = 9 = (1001)2, then lowbit(9) = 1. Assign n to 9 – lowbit(9) = 8 = (1000)2, then lowbit(8) = 8 = (1000)2. At this time 8 – lowbit(8) = 0, stop looping. In the whole process, two numbers, 1 and 8 = (1000)2, are subtracted, which are exactly the values obtained by adding 0 after the 1 in each bit of n. Taking the logarithms of 1 and 8, log21 and log28, we get

n

n

Bits 0 and 3 of n are 1. Because the log function of the C++ math.h library is a real number operation with e as the base, and the complexity constant is large, it is necessary to preprocess an array and replace the log operation with the Hash method.

When n is small, the simplest method is to directly create an array H, let H[2k] = k, the program is as follows:

const int MAX_N = 1 << 20;
int H[MAX_N + 1];
for (int i = 0; i <= 20; i + + ) H[1 << i] = i; //Preprocessing
while (cin >> n) {<!-- -->
while (n > 0) {<!-- -->
cout << H[n & amp; -n] << ' ';
n -= n & -n;
}
cout << endl;
}

A slightly more complicated but more efficient method is to create an array H of length 37, let H[2 k mod 37] = k. A little math trick is used here:

?

k

[

0

,

35

]

,

2

k

\forall k \in [0,35], 2^k

?k∈[0,35],2k mod 37 are not equal to each other, and they take exactly 1 ~ 36. The required procedures are:

int H[37];
for (int i = 0; i < 36; i + + ) H[(1ll << i) % 37] = i;

while (cin >> n) {<!-- -->
while (n > 0) {<!-- -->
cout << H[n & amp; -n] << ' ';
n -= n & amp; -n;
    }
    cout << endl;
}

It is worth pointing out that lowbit operation is also a basic operation in tree arrays.