Bitset
类模板 bitset
表示一个 位的固定大小序列。可以用标准逻辑运算符操作 bitset
,并将它与字符串和整数相互转换。对于字符串表示和移位操作的列举方向来说,这个序列被当做最低索引元素位于右侧,类似于整数的二进制表示。
满足可复制构造 () 及可复制赋值 () 的要求。
template< std::size_t N > class bitset;
模板形参
- 要为 分配存储的位数
eg: bitset<N> ac
; 声明一个大小为的bitset
,名称为
std::bitset
的全部成员函数均为 :在常量表达式求值中创建并使用std::bitset
对象是可能的。(C++23 起) 所以不要试图使用这种方式!!!
常用操作
count()
返回 的个数,时间复杂度为 ,其中 为机器字长,一般为 位,评测机一般为 位;有一类似操作__builtin_popcount(val)/__builtin_popcountll(val)
,时间复杂度相同(应该是)。operator<<=
operator>>=
operator<<
operator>>
进行二进制左移和右移operator=
赋值operator[]
访问指定的位operator&=
operator|=
operator^=
operator~
进行二进制与、或、异或及非
注解
若某个位集合在编译时大小未知,或者必须在运行时改变其大小,则可代之以使用 std::vector<bool>
或 boost::dynamic_bitset
之类的动态类型。
功能特性测试宏 | 值 | 标准 | 功能特性 |
---|---|---|---|
__cpp_lib_constexpr_bitset | 202207L | (C++23) | 使 std::bitset 更 constexpr |
__cpp_lib_bitset | 202306L | (C++26) | std::bitset 的 std::string_view 接口 |
示例
#include <bitset>#include <cassert>#include <cstddef>#include <iostream>
int main(){ typedef std::size_t length_t, position_t; // 提示
// 构造函数: constexpr std::bitset<4> b1; constexpr std::bitset<4> b2{0xA}; // == 0B1010 std::bitset<4> b3{"0011"}; // C++23 起也可以为 constexpr std::bitset<8> b4{"ABBA", length_t(4), /*0:*/'A', /*1:*/'B'}; // == 0B0000'0110
// 能打印出 bitset 到流: std::cout << "b1:" << b1 << "; b2:" << b2 << "; b3:" << b3 << "; b4:" << b4 << '\n';
// bitset 支持逐位运算: b3 |= 0b0100; assert(b3 == 0b0111); b3 &= 0b0011; assert(b3 == 0b0011); b3 ^= std::bitset<4>{0b1100}; assert(b3 == 0b1111);
// 整个集合上的操作: b3.reset(); assert(b3 == 0); b3.set(); assert(b3 == 0b1111); assert(b3.all() && b3.any() && !b3.none()); b3.flip(); assert(b3 == 0);
// 单独位上的操作: b3.set(position_t(1), true); assert(b3 == 0b0010); b3.set(position_t(1), false); assert(b3 == 0); b3.flip(position_t(2)); assert(b3 == 0b0100); b3.reset(position_t(2)); assert(b3 == 0);
// 支持下标 operator[]: b3[2] = true; assert(true == b3[2]);
// 其他操作: assert(b3.count() == 1); assert(b3.size() == 4); assert(b3.to_ullong() == 0b0100ULL); assert(b3.to_string() == "0100");}
Output:
b1:0000; b2:1010; b3:0011; b4:00000110
Hash
普通哈希的优化
template<typename tp1,typename tp2,int N>struct Htb{ static constexpr int M=1e7+19; int hd[M+3],to[N],ct; tp1 ed[N];tp2 w[N]; static int hc(ul v){ v^=v<<13,v^=v>>7; return (v^(v<<17))%M; } void ins(tp1 x,tp2 y){ int &p=hd[hc(x)]; ed[++ct]=x,to[ct]=p; w[p=ct]=y; } int count(tp1 x){ for(int i=hd[hc(x)];i;i=to[i]) if(ed[i]==x)return 1; return 0; } pair<tp2,bool>find(tp1 x){ for(int i=hd[hc(x)];i;i=to[i]) if(ed[i]==x)return mkp(w[i],true); return mkp(tp2(),false); } int operator[](tp1 x){ int &p=hd[hc(x)]; for(int i=p;i;i=to[i]) if(ed[i]==x)return i; ed[++ct]=x,to[ct]=p; return p=ct; } void clear(){while(ct)hd[hc(ed[ct--])]=0;}};
这种多维哈希相对于普通哈希的优点:
- 内存局部性更好,有助于缓存友好。
- 操作可以非常高效,只需遍历已有的节点更新 数组
- 代码中手动维护链表,便于对冲突处理和自定义哈希函数调优
使用方法:
struct hc
: 随机映射,把一个ul类型随机映射到 ~ 中的一个随机数struct ins
类似邻接表插入count
是否找到pair<tp2,bool>
第一位查询得到的值,bool是否找到了operator[]
用于寻找第一维的标号,未找到则创建一个
缺点:
- 由于是 ,在 上写依然会被叉
- 第一维不能传 ,可以另写
static ul hc(string s) {}
Hash的再优化
为了解决上述 的缺点,我们可以引入随机 ,同时引入 库中封装好的gp_hash_table
。
struct myhash { static uint64_t hash(uint64_t x) { x += 0x9e3779b97f4a7c15; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; return x ^ (x >> 31); } size_t operator()(uint64_t x) const { static const uint64_t SEED = chrono::steady_clock::now().time_since_epoch().count(); return hash(x + SEED); } size_t operator()(pair<uint64_t, uint64_t> x) const { static const uint64_t SEED = chrono::steady_clock::now().time_since_epoch().count(); return hash(x.first + SEED) ^ (hash(x.second + SEED) >> 1); }};uint64_t string_hash(const string &s) { const uint64_t BASE = 131; uint64_t h = 0; for (char c : s) { h = h * BASE + c; } return h;}#define mymap __gnu_pbds::gp_hash_table
定义方法
mymap<int, int, myhash> dic;
mymap<int, null_type, myhash> dic;
unordered_map<int, int, myhash> dic;
第二维表示存或者不存,若为null_type
则不关心是否存值。
Pbds
介绍
什么是__gnu_pbds
? !简称平板电视 。在使用前,你需要:
#include<ext/pb_ds/assoc_container.hpp>#include<ext/pb_ds/tree_policy.hpp>//用tree#include<ext/pb_ds/hash_policy.hpp>//用hash#include<ext/pb_ds/trie_policy.hpp>//用trie#include<ext/pb_ds/priority_queue.hpp>//用priority_queueusing namespace __gnu_pbds;
这么长?没事,有更简单的:
#include<bits/extc++.h>using namespace __gnu_pbds;
中的所有内容均不在头文件#include <bits/stdc++.h>
中,也不再 命名空间中。
pbds::hash
上面已经介绍过。
该题使用中自带的,时间复杂度仅为。
tree
平衡树:使得集中的元素始终保持有序,并且支持快速插入和删除某个数,和快速索引排名为 的数,作增删改查操作。
显然, 无法实现快速索引某个数的排名和快速索引第 位数。
通俗且不严谨的说, 不像数组一样拥有“下标”,而 库中的平衡树既有 的功能,也有数组的下标。
平衡树的tag
rb_tree_tag
:红黑树splay_tree_tag
:伸展树ov_tree_tag
:有序向量树(不建议使用)
一般推荐使用rb_tree_tag
定义方式
tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> tr;
重要函数
tr.find_by_order(k)
返回下标为 的对象的指针tr.order_of_key(val)
返回小于等于 的对象的个数tr.lower(upper)_bound(val)
返回大于等于(大于) 的数的对象的指针tr.insert(val)
向 中插入A.join(B)
将 整个并入tr.size
查询 的大小tr.erase(tr.lower_bound(val))
删除 中大于等于 的值tr.erase(val)
删除 中值为 的元素
创建multiset
一般来说有如下两种方法:
- 插入时左移 位,并加上一个独一无二的值,删除时右移 位取出
- 在创建时,将
less<int>
改为less_equal<int>
方便但非常不推荐!!!
priority_queue
堆的tag
pairing_heap_tag
:使用配对堆()实现。配对堆是一种具有良好理论性能和实际性能的堆结构,通常在实践中表现优异。binary_heap_tag
:使用二叉堆()实现。二叉堆是一种经典的堆结构,通常用于实现优先队列。binomial_heap_tag
:使用二项堆()实现。二项堆是一种支持高效合并操作的堆结构。rc_binomial_heap_tag
:使用放宽的二项堆()实现。这种堆结构在二项堆的基础上进行了优化,以提高某些操作的性能。thin_heap_tag
:使用瘦堆()实现。瘦堆是一种专门为算法设计的堆结构,适用于图算法中的优先队列。
一般推荐使用pairing_heap_tag
定义方式
priority_queue<int, less<int>, pairing_heap_tag> q1, q2;
重要函数
q1.push(val)
a1.size()
q1.pop()
q1.join(q2);
q1.empty();
提示
使用库中的priority_queue
时,由于和 库中的priority_queue
重名,会报错,应当主动声明命名空间。