主要做ai解答的记录
哈希模板
[cf大佬博客](Blowing up unordered_map, and how to stop getting hacked on it - Codeforces)
1. 标准 unordered_map 安全增强版
这是在比赛中最通用的写法,兼容所有现代 C++ 编译器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream> #include <unordered_map> #include <chrono>
using namespace std;
struct custom_hash { static uint64_t splitmix64(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 FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count(); return splitmix64(x + FIXED_RANDOM); } };
int main() { unordered_map<long long, long long, custom_hash> safe_map;
int n; cin >> n;
for (int i = 0; i < n; i++) { long long x, y; cin >> x >> y;
if (safe_map.count(x)) { cout << "f(" << x << ") 的旧值是: " << safe_map[x] << endl; } else { cout << "f(" << x << ") 之前没有值,默认为 0" << endl; }
safe_map[x] = y; }
return 0; }
|
2. 进阶版:高性能 gp_hash_table(最快选择)
gp_hash_table,这是 C++ 扩展库里的“神级”哈希表。它比 unordered_map 快好几倍,但它非常容易被卡(因为它使用 2 的幂次作为桶大小)。所以它必须配合 custom_hash 使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream> #include <chrono>
#include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/hash_policy.hpp>
using namespace std; using namespace __gnu_pbds;
struct custom_hash { static uint64_t splitmix64(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 FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count(); return splitmix64(x + FIXED_RANDOM); } };
int main() { gp_hash_table<long long, int, custom_hash> fast_safe_table;
fast_safe_table[123456789012345LL] = 1; cout << fast_safe_table[123456789012345LL] << endl;
return 0; }
|
为什么要这样写?(总结)
- 防止碰撞攻击:你定义的
FIXED_RANDOM 每次运行都不一样。即使出题人知道你用了 splitmix64,他也没法构造出让你冲突的数据,因为他不知道你那一秒钟的 chrono 种子是多少。
- 通过高难度子任务:如果专门卡
unordered_map。你只要加上这个 custom_hash,原本超时的代码可能直接就 AC (Accepted) 了。
- 万能模板:这个
struct custom_hash 。以后只要题目可美美保存下来。涉及到 long long 类型的哈希映射,直接把它扔进代码开头即可。
模板应用
[洛谷P11615](P11615 【模板】哈希表 - 洛谷)
![[Pasted image 20260504091237.png]]
为了苟对,需要把刚才学到的 “快读” + “手写线性探测哈希” + “SplitMix64位扰动” 结合在一起。
解题核心逻辑
- 数据类型:全部使用
unsigned long long (简称 ull),因为它正好是 64 位,且在 C++ 中 ull 的溢出自动相当于对 $2^{64}$ 取模。
- 哈希冲突处理:题目明确说了要 hack 标准库,所以我们手动实现一个闭散列(线性探测)哈希表。
- 哈希函数:使用
splitmix64 逻辑,确保 $x$ 被打得足够碎。
- 快速读入:题目给出的
rd() 函数必须写,否则 500 万数据读入就会超时。
完整的实践代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <cstdio> #include <iostream>
using namespace std;
typedef unsigned long long ull;
char buf[1 << 23], *p1 = buf, *p2 = buf; #define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline ull rd() { ull x = 0; char ch = gc(); while (ch < '0' || ch > '9') { if (ch == EOF) return 0; ch = gc(); } while (ch >= '0' && ch <= '9') { x = x * 10 + (ch - '0'); ch = gc(); } return x; }
const int HashSize = 1 << 24; const int Mask = HashSize - 1;
ull keys[HashSize]; ull vals[HashSize]; bool used[HashSize];
inline ull splitmix64(ull x) { x += 0x9e3779b97f4a7c15; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; return x ^ (x >> 31); }
ull query_and_set(ull x, ull y) { int pos = splitmix64(x) & Mask; while (used[pos]) { if (keys[pos] == x) { ull old_v = vals[pos]; vals[pos] = y; return old_v; } pos = (pos + 1) & Mask; } used[pos] = true; keys[pos] = x; vals[pos] = y; return 0; }
int main() { int n = (int)rd(); ull final_ans = 0;
for (int i = 1; i <= n; i++) { ull x = rd(); ull y = rd();
ull ans_i = query_and_set(x, y); final_ans += (ull)i * ans_i; }
printf("%llu\n", final_ans);
return 0; }
|
为什么这段代码能通过“子任务 4”?(AI如是答
- 线性探测 vs 拉链法:
标准库 unordered_map 是拉链法,它需要为每个节点分配内存。在 $N=5 \times 10^6$ 时,频繁的内存分配和指针跳转(Cache Miss)非常慢。我们手写的数组线性探测是连续内存,CPU 访问极快。
splitmix64 的威力:
Subtask 4 的 Hack 数据通常是故意构造的“等差数列”或者“特定质数的倍数”。splitmix64 这种位运算组合能把这些规律彻底打碎,让它们在 HashSize 范围内分布得非常均匀,几乎不产生冲突。
- Mask 取模优化:
我们把 HashSize 设为 $2$ 的幂次,通过 & Mask 快速得到下标。这比 % 运算要快得多,在千万级计算中能省下大量时间。