位运算三则
大概是一篇介绍如何简单在 WRM 下实现
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
我们先来解决一个比较简单的问题:给定
int ut_lg64(u64 x) {
constexpr static u64 mp[]{63, 0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54,
33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 30,
34, 11, 43, 14, 22, 4, 62, 57, 46, 52, 38, 26, 32,
41, 50, 36, 17, 19, 29, 10, 13, 21, 56, 45, 25, 31,
35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5},
id = 0x07edd5e59a4e28c2ull;
return mp[x * id >> 58];
}
解决了这个问题之后二进制最低位是送的。不会的话左转树状数组题解区。
int ut_lglbt64(u64 x) { return ut_lg64(x & -x); }
接下来我们要解决的问题是 popcount。我们先会有一个比较简单的并行思路。初始时,
x -= (x & 0xaaaaaaaaaaaaaaaaull) >> 1;
x = (x & 0x3333333333333333ull) + ((x & 0xccccccccccccccccull) >> 2);
然而这样迭代下去的复杂度是
int ut_popcnt64(u64 x) {
x -= (x & 0xaaaaaaaaaaaaaaaaull) >> 1;
x = (x & 0x3333333333333333ull) + ((x & 0xccccccccccccccccull) >> 2);
x = ((x * 0x11) & 0xf0f0f0f0f0f0f0f0ull) >> 4;
return (x * 0x0101010101010101ull) >> 56;
}
接下来我们要解决一个数二进制最高位。考虑对
我们先来第一个问题,判断数在哪一块。我们把这部分分为两个部分:把所有有值的块化成某个固定形式,根据固定形式找值。我们这里的固定形式是:一个块有值当且仅当其及其之后的所有块最高位为一。
首先我们考虑怎么化形式。第一步我们需要先把一个块有值当且仅当这个块最高位为一这个事情给做出来。
方便起见我们假设所有块的最高位为
u64 ut_blkpstv(u64 x) {
x = (x & 0x7f7f7f7f7f7f7f7full) | (x & 0x8080808080808080ull) >> 1;
return (x | 0x8080808080808080ull) - 0x0101010101010101ull;
}
然后我们考虑怎么进一步推到需要的形式。我们先把每一块最高位的一提取出来(其他位置置零)并把它们搬运到对应块的最低位。然后我们制备一个二进制数,满足第
我们对这个新数再做一次上一步的化形式。不难发现,此时有值的块构成后缀,也就是说这个更新的的数恰好一个后缀的块最高位为一。我们完成了我们的目标。
接下来我们考虑给定化好形式的数怎么做。不难发现把每一块最高位的一搬到最低位并把其它位置零,然后制备一整块的一乘上去,得到二的幂减一,可以直接处理了。
int ut_blkfst(u64 x) {
x = x >> 7 & 0x0101010101010101ull;
return (ut_lg64(x * 0xff + 1) >> 3) - 1;
}
于是我们接下来只需要解决
完整代码如下:
u64 ut_blkpstv(u64 x) {
x = (x & 0x7f7f7f7f7f7f7f7full) | (x & 0x8080808080808080ull) >> 1;
return (x | 0x8080808080808080ull) - 0x0101010101010101ull;
}
int ut_blkfst(u64 x) {
x = x >> 7 & 0x0101010101010101ull;
return (ut_lg64(x * 0xff + 1) >> 3) - 1;
}
int ut_bsrsi8(u8 x) {
if (x >> 7) return 7;
u64 y = (x | 0x80) * 0x0101010101010101ull;
return ut_blkfst(y - 0x8040201008040201ull);
}
int ut_bsrsi64(u64 x) {
u64 y = ut_blkpstv(x) >> 7 & 0x0101010101010101ull;
int c = (y >> 32) ? (y >>= 32, 8) : 0;
c += ut_blkfst(ut_blkpstv(y * 0x01010101 >> 24)) << 3;
return c | ut_bsrsi8(x >> c);
}
以上。