Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
1652 字
8 分钟
Bitset Hash Pbds

Bitset#

类模板 bitset 表示一个 NN 位的固定大小序列。可以用标准逻辑运算符操作 bitset,并将它与字符串和整数相互转换。对于字符串表示和移位操作的列举方向来说,这个序列被当做最低索引元素位于右侧,类似于整数的二进制表示。

bitsetbitset 满足可复制构造 (CopyConstructibleCopyConstructible) 及可复制赋值 (CopyAssignableCopyAssignable) 的要求。

template< std::size_t N > class bitset;

模板形参#

NN - 要为 bitsetbitset 分配存储的位数

eg: bitset<N> ac; 声明一个大小为NNbitset,名称为acac

  • std::bitset 的全部成员函数均为 constexprconstexpr:在常量表达式求值中创建并使用 std::bitset 对象是可能的。(C++23 起) 所以不要试图使用这种方式!!!

常用操作#

  1. count() 返回 11 的个数,时间复杂度为 O(nw)O(\frac{n}{w}),其中 ww 为机器字长,一般为 6464 位,评测机一般为 3232 位;有一类似操作__builtin_popcount(val)/__builtin_popcountll(val),时间复杂度相同(应该是)。
  2. operator<<= operator>>= operator<< operator>> 进行二进制左移和右移
  3. operator= 赋值
  4. operator[] 访问指定的位
  5. operator&= operator|= operator^= operator~ 进行二进制与、或、异或及非

注解#

若某个位集合在编译时大小未知,或者必须在运行时改变其大小,则可代之以使用 std::vector<bool>boost::dynamic_bitset 之类的动态类型。

功能特性测试宏标准功能特性
__cpp_lib_constexpr_bitset202207L(C++23)使 std::bitset 更 constexpr
__cpp_lib_bitset202306L(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;}
};

这种多维哈希相对于普通哈希的优点:

  1. 内存局部性更好,有助于缓存友好。
  2. clearclear 操作可以非常高效,只需遍历已有的节点更新 hdhd 数组
  3. 代码中手动维护链表,便于对冲突处理和自定义哈希函数调优

使用方法:

  1. struct hc: xorshiftxorshift 随机映射,把一个ul类型随机映射到00 ~ M1M-1中的一个随机数
  2. struct ins类似邻接表插入
  3. count是否找到
  4. pair<tp2,bool> 第一位查询得到的值,bool是否找到了
  5. operator[] 用于寻找第一维的标号,未找到则创建一个

缺点:

  1. 由于是 xorshiftxorshift,在 cfcf 上写依然会被叉
  2. 第一维不能传 stringstring,可以另写static ul hc(string s) {}

Hash的再优化#

为了解决上述 HashHash 的缺点,我们可以引入随机 HashHash,同时引入 PbdsPbds 库中封装好的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?PolicyPolicy basedbased datadata structuresstructures!简称平板电视 pbdspbds。在使用pbdspbds前,你需要:

#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_queue
using namespace __gnu_pbds;

这么长?没事,有更简单的:

#include<bits/extc++.h>
using namespace __gnu_pbds;

PbdsPbds中的所有内容均不在头文件#include <bits/stdc++.h>中,也不再 stdstd 命名空间中。

pbds::hash#

上面已经介绍过。

P1333 瑞瑞的木棍

该题使用PbdsPbds中自带的HashHash,时间复杂度仅为O(n)O(n)

tree#

平衡树:使得集中的元素始终保持有序,并且支持快速插入和删除某个数,和快速索引排名为 xx 的数,作增删改查操作。

显然,setset 无法实现快速索引某个数的排名和快速索引第 kk 位数。

通俗且不严谨的说,setset 不像数组一样拥有“下标”,而 pbdspbds 库中的平衡树既有 setset 的功能,也有数组的下标。

平衡树的tag#

  1. rb_tree_tag:红黑树
  2. splay_tree_tag:伸展树
  3. ov_tree_tag:有序向量树(不建议使用)

一般推荐使用rb_tree_tag

定义方式#

tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> tr;

重要函数#

  1. tr.find_by_order(k) 返回下标为 kk 的对象的指针
  2. tr.order_of_key(val) 返回小于等于 valval 的对象的个数
  3. tr.lower(upper)_bound(val) 返回大于等于(大于)valval 的数的对象的指针
  4. tr.insert(val)trtr 中插入 valval
  5. A.join(B)BB 整个并入AA
  6. tr.size 查询 trtr 的大小
  7. tr.erase(tr.lower_bound(val)) 删除 trtr 中大于等于 valval 的值
  8. tr.erase(val) 删除 trtr 中值为 valval 的元素

创建multiset#

一般来说有如下两种方法:

  1. 插入时左移 2020 位,并加上一个独一无二的值,删除时右移 2020 位取出
  2. 在创建时,将 less<int> 改为 less_equal<int> 方便但非常不推荐!!!

priority_queue#

堆的tag#

  1. pairing_heap_tag:使用配对堆(PairingHeapPairing Heap)实现。配对堆是一种具有良好理论性能和实际性能的堆结构,通常在实践中表现优异。
  2. binary_heap_tag:使用二叉堆(BinaryHeapBinary Heap)实现。二叉堆是一种经典的堆结构,通常用于实现优先队列。
  3. binomial_heap_tag:使用二项堆(BinomialHeapBinomial Heap)实现。二项堆是一种支持高效合并操作的堆结构。
  4. rc_binomial_heap_tag:使用放宽的二项堆(RelaxedBinomialHeapRelaxed Binomial Heap)实现。这种堆结构在二项堆的基础上进行了优化,以提高某些操作的性能。
  5. thin_heap_tag:使用瘦堆(ThinHeapThin Heap)实现。瘦堆是一种专门为DijkstraDijkstra算法设计的堆结构,适用于图算法中的优先队列。

一般推荐使用pairing_heap_tag

定义方式#

priority_queue<int, less<int>, pairing_heap_tag> q1, q2;

重要函数#

  1. q1.push(val)
  2. a1.size()
  3. q1.pop()
  4. q1.join(q2);
  5. q1.empty();

提示#

使用PbdsPbds库中的priority_queue时,由于和 stdstd 库中的priority_queue重名,会报错,应当主动声明命名空间。

Bitset Hash Pbds
https://mizuki.mysqil.com/posts/bitset-hash-pbds/
作者
LANSGANBS
发布于
2025-03-30
许可协议
CC BY-NC-SA 4.0
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00