Disclaimer: It might have bugs, don't send me death threats if you FST.
I couldn't find a nice dynamic bitset template so I wrote one.
It can be found here.
It has additional functionality as compared to std::bitset
(you can answer many kinds of range queries on it, for example: "Find $$$k$$$-th set bit in range $$$[l, r]$$$).
Some poor documentationGeneral
- It is used to store a 0-indexed boolean array.
- Just like regular bitset, index 0 is the rightmost (sense of direction is important for
>>
and <<
). - It uses $$$\lceil n/B \rceil$$$ blocks of size $$$B$$$ (which should be equal to bitwidth of $$$T$$$) to represent a boolean array of length $$$n$$$. Note that there may be some unused bits at the end of the last block. We will always have these unused bits set to 0.
Functions
For most functions, their use is obvious. For others:
helper functions T prefix(int i)
: returns block of size $$$B$$$ with only first $$$i$$$ bits set. T suffix(int i)
: returns block of size $$$B$$$ with only last $$$i$$$ bits set. T range(int l, int r)
: returns block of size $$$B$$$ with only bits in range $$$[l, r]$$$ set (note that this function is 1-indexed but everything else is 0-indexed). T submask(int l, int r)
: given that $$$l$$$ and $$$r$$$ belong to the same block $$$x$$$, returns $$$x$$$ with all bits outside $$$[l, r]$$$ turned off. trim()
: turns off extra bits in the last block, if any.
main functions o=
operator where o
is some bitwise operator: if you do a o= b
where a
and b
are bitsets of different sizes, the operation is done assuming that the smaller bitset is padded with 0s to make the sizes of the two equal. Count()
: returns total number of set bits. FindFirst()
: returns position of first set bit (returns -1 if no such bit). FindLast()
: returns position of last set bit (returns -1 if no such bit). RangeProcess(int l, int r, auto block_brute, auto block_quick)
: can be used to process some range queries on the bitset. For example, "Finding $$$k$$$-th set bit in range $$$[l, r]$$$". See implementation of below functions to understand how it works. RangeSet(int l, int r, bool val)
: set all bits in given range to given value. Count(int l, int r)
: count number of set bits in given range. FindFirst(int l, int r)
: find position of first set bit in given range (returns -1 if no such bit). FindLast(int l, int r)
: find position of last set bit in given range (returns -1 if no such bit).
Warnings
- Always keep in mind that there may be some unused 0s at the end of the last block.
Efficiency:
Firstly, always use the following pragmas with it:
pragmas#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
They can reduce runtime by upto 50% (thanks to mr rewhile for enlightening me on this).
I am too lazy to run any proper benchmarks, but I solved a few problems with it and it was always faster than std::bitset
and tr2::dynamic_bitset
. Here are some sets of submissions on the same problem with all 3:
1. Using &=
, |
and >>
- My bitset: 284156267
std::bitset
: 284156622 tr2::dynamic_bitset
: 284156883
Bitset | Time | Memory |
My bitset | 765 ms | 944 KB |
std::bitset | 859 ms | 1628 KB |
tr2::dynamic_bitset | 1077 ms | 1240 KB |
2. Using &=
, >>=
edit: Redid these because apparently the server was under high load at the time of the initial submissions.
- My bitset: 284262107
std::bitset
: 284277251 tr2::dynamic_bitset
: 284267738
Bitset | Time | Memory |
My bitset | 343 ms | 1124 KB |
std::bitset | 405 ms | 1140 KB |
tr2::dynamic_bitset | 390 ms | 844 KB |
So it seems that my bitset is as good or slightly better in every manner. I have no idea why this is the case though, as there is nothing which seems particularly faster in my implementation.
Parting notes:
- If you use it and find some bugs, let me know.
- If you think it's missing some significant functionality, let me know.
Thanks for reading my blog.
Bitset Waifu
thanks to degenerates on AC
Note: I will optimize it with SIMD and make shifts lazy in a couple of days, so it might become faster.
Nice!!
Nice