基于std::bitset的异或最优解问题:效率与简洁实现探讨
关于std::bitset在N位序列异或求最小1个数的问题
需求与现有代码
需要找出一个N位(N最大为50)的二进制序列(用std::bitset<N>存储),与一系列前缀全1序列(从0b0、0b1、0b11,直到与目标序列最高位对齐的全1序列)异或后,结果中1的个数的最小值。
现有实现代码如下:
#include <bitset> #include <vector> #include <algorithm> #include <iostream> #include <bit> constexpr std::size_t N = 8; std::vector<int> countPenalty(std::bitset<N> set) { std::bitset<N> closing{0b0}; std::bitset<N> eins{0b1}; std::vector<int> countPen; countPen.reserve(N); while(std::bit_width(closing.to_ulong()) <= std::bit_width(set.to_ulong())) { std::bitset<N> tempSet = set ^ closing; countPen.push_back(std::popcount(tempSet.to_ulong())); closing <<= 1; closing |= eins; } return countPen; } int main() { std::string s{"-11--111"}; std::bitset<N> visitorSet(s, 0, s.size(), '0', '1'); std::vector<int> penalties = countPenalty(visitorSet); auto minRes = std::min_element(penalties.begin(), penalties.end()); std::cout << *minRes << std::endl; // 输出2 return 0; }
实际业务场景
该问题对应售票柜台的最优闭店策略:
- 两类损失:柜台营业但无顾客、柜台关闭但有顾客
- 每日时段顾客记录用二进制表示(1=有顾客,0=无顾客),闭店记录用二进制表示(1=闭店,0=营业)
- 顾客记录与闭店记录的异或结果中,1的个数即为总损失次数,目标是找到损失最小的闭店方案。
核心疑问解答
1. 大尺寸std::bitset效率不高的原因
- 类型转换开销:现有代码频繁调用
to_ulong()将bitset转换为unsigned long,当N接近平台字长(比如50位接近64位),转换需要拼接内部存储的多个字,带来额外开销;若N超过64位,to_ulong()会直接溢出或抛出异常,存在隐患。 - 编译期固定大小限制:
std::bitset的大小N是编译期常量,内部用数组存储位数据,当N不是平台字长的整数倍时,位运算需要额外的边界掩码处理,相比原生整数位运算有冗余操作。 - API适配问题:
std::popcount等C++20位操作函数对原生整数更友好,bitset需要先转换才能使用,间接增加了开销。
2. 更简洁高效的实现方式
针对N最大50的场景,用64位整数(uint64_t)直接处理是最优选择,避免bitset的类型转换和模板限制:
#include <vector> #include <bit> #include <algorithm> #include <iostream> std::vector<int> countPenalty(uint64_t set) { std::vector<int> countPen; const auto max_bits = std::bit_width(set); countPen.reserve(max_bits + 1); // 包含0个1的情况 uint64_t closing = 0; do { countPen.push_back(std::popcount(set ^ closing)); closing = (closing << 1) | 1; } while (std::bit_width(closing) <= max_bits); return countPen; } int main() { // 把字符串转换为uint64_t,处理占位符'-'为0 std::string s{"-11--111"}; uint64_t visitorSet = 0; for (char c : s) { visitorSet <<= 1; if (c == '1') visitorSet |= 1; // '-'视为0,不做额外操作 } std::vector<int> penalties = countPenalty(visitorSet); auto minRes = std::min_element(penalties.begin(), penalties.end()); std::cout << *minRes << std::endl; // 输出2 return 0; }
如果坚持用std::bitset,也可以优化掉类型转换,直接用bitset自带的count()方法统计1的个数:
template<std::size_t N> std::vector<int> countPenalty(const std::bitset<N>& set) { std::vector<int> countPen; const auto max_bits = std::bit_width(static_cast<uint64_t>(set.to_ullong())); countPen.reserve(max_bits + 1); std::bitset<N> closing; do { countPen.push_back((set ^ closing).count()); closing = (closing << 1) | 1; } while (std::bit_width(static_cast<uint64_t>(closing.to_ullong())) <= max_bits); return countPen; }
3. C++20环境下std::bitset是否仍合适
- 若N是编译期固定值且不超过64位,
std::bitset可以使用,但效率不如原生64位整数;如果需要编译期计算,std::bitset的constexpr API(C++20支持count()、位运算等constexpr操作)有一定优势。 - 若N可能超过64位,
std::bitset的类型转换会出现溢出问题,此时更适合用std::vector<bool>或动态位集库;但本场景N最大50,64位整数完全覆盖,原生整数实现更简洁高效。
内容的提问来源于stack exchange,提问作者Suslik
相关产品推荐
相关产品推荐

