P11615 【模板】哈希表 题解
_fairytale_ · · 题解
前言
在 THUWC2025 D1T2 中,作者的
HACKS
这里讲一下 hack 是怎么造的以及大概原理,想要了解更多,推荐阅读深度好文 Blowing up unordered_map, and how to stop getting hacked on it。
unordered_map
unordered_map 使用的是将键值模一个质数的哈希方式,其中质数来自一个质数表 __prime_list,发现其中只有 256 个质数。
但是据说在不同的地方选的质数不一样,那不管你选啥质数了,直接造四组数据,每组数据选其中的
gp_hash_table/cc_hash_table
这两个比较难绷,哈希方式是模
如何避免被卡?
unordered_map
这个比较简单,直接把所有键值异或上一个随机数即可破坏 hack 数据的同余性质。
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct Hsh{
ull operator ()(const ull&x)const{
static const ull r=rnd();
return x^r;
}
};
unordered_map<ull,ull,Hsh>f;
但是 unordered_map 还有一个缺陷就是跑得太慢,在本题要跑 2s 以上,虽然本题不卡常,但是我们总会在某个时候需要更快的哈希表。
gp_hash_table/cc_hash_table
这两个因为本来就是模
在 CF 上的那篇文章里作者使用了 splitmix64 作为哈希函数:
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
// http://xorshift.di.unimi.it/splitmix64.c
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 FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
但是这里的三个神秘数字有点难背,有什么更好记的写法吗?
有的兄弟,有的。我们直接用随机数替代神秘数字:
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct Hsh{
ull operator ()(ull x)const{
static const ull s1=rnd(),s2=rnd(),s3=rnd();
x+=s1;
x=(x^(x>>33))*s2;
x=(x^(x>>30))*s3;
return x;
}
};
gp_hash_table<ull,ull,Hsh>f;
实测这样和 splitmix64 效果差不多。
这样修改过的 gp_hash_table 在本题跑了 1s 左右,而 cc_hash_table 却跟 unordered_map 效率一样。
手写哈希表
有时我们需要更小的常数,这个时候就需要手写哈希表,这里就不多介绍了。
const int LEN=(1<<23);
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct HashMap {
inline ull F(ull x) {
static const ull s1=rnd(),s2=rnd(),s3=rnd();
x+=s1;
x=(x^(x>>33))*s2;
x=(x^(x>>30))*s3;
return x;
}
bitset<LEN+5>hav;
ull key[LEN+5];
ull value[LEN+5];
int gt(const ull&x) {
int i=F(x)&(LEN-1);
while(hav[i]&&key[i]!=x)i=(i+1)&(LEN-1);
hav[i]=1,key[i]=x;
return i;
}
ull& operator[](const ull&x) {
return value[gt(x)];
}
}f;
但是我写的好像比较烂,只比 gp_hash_table 快了 100ms 左右,欢迎大家分享自己更快的哈希表!
结语
愿世界上没有卡常题。