从 map 到 pbds hash。

前言

最近重新整理 STL 的时候,在 map 和 umap 处有了一些讨论,然后又去看了 pbds 中的哈希表,在此稍作总结。

本文讨论基于该题:P11615 【模板】哈希表。

map

map 是 STL 提供的一种有序键值对,内部实现通常是红黑树。

其大多数的单次操作的复杂度均为 \(O(\log n)\),在本题中效率过低。

较为常用,不再赘述。

#include <bits/stdc++.h>
using namespace std;
using ui64 = unsigned long long;
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 unsigned long long rd()
{ // 读入一个 64 位无符号整数
    unsigned long long x = 0;
    char ch = gc();
    while (!isdigit(ch))
        ch = gc();
    while (isdigit(ch))
        x = x * 10 + (ch ^ 48), ch = gc();
    return x;
}
map<ui64, ui64> t;
void _()
{
    int n = rd();
    ui64 ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        ui64 x = rd(), y = rd();
        ans += t[x] * i;
        t[x] = y;
    }
    cout << ans << '\n';
}
int main()
{
    int T = 1;
    // cin >> T;
    while (T--)
        _();
    return 0;
}

umap

umap 全称 unordered_map,是 STL 提供的一种无序键值对,内部实现基于哈希表。

通常使用方式同 map,即:

unordered_map<ui64, ui64> t;

没有刻意设计的数据下,umap 的复杂度为 \(O(1)\)。

通过制造哈希冲突,在最劣数据下,umap 的复杂度为 \(O(n)\)。

#include <bits/stdc++.h>
using namespace std;
using ui64 = unsigned long long;
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 unsigned long long rd()
{ // 读入一个 64 位无符号整数
    unsigned long long x = 0;
    char ch = gc();
    while (!isdigit(ch))
        ch = gc();
    while (isdigit(ch))
        x = x * 10 + (ch ^ 48), ch = gc();
    return x;
}
unordered_map<ui64, ui64> t;
void _()
{
    int n = rd();
    ui64 ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        ui64 x = rd(), y = rd();
        ans += t[x] * i;
        t[x] = y;
    }
    cout << ans << '\n';
}
int main()
{
    int T = 1;
    // cin >> T;
    while (T--)
        _();
    return 0;
}

自定义哈希函数

umap 支持自定义哈希函数,可以通过随机数来降低哈希冲突。

如:

mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct rnd_hash
{
    ui64 operator()(const ui64 &x) const
    { // 重载运算符实现自定义哈希函数
        static const ui64 r = rnd();
        return x ^ r;
    }
};
unordered_map<ui64, ui64, rnd_hash> t;

不过仍有被卡的风险便是了。

#include <bits/stdc++.h>
using namespace std;
using ui64 = unsigned long long;
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 unsigned long long rd()
{ // 读入一个 64 位无符号整数
    unsigned long long x = 0;
    char ch = gc();
    while (!isdigit(ch))
        ch = gc();
    while (isdigit(ch))
        x = x * 10 + (ch ^ 48), ch = gc();
    return x;
}
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct rnd_hash
{
    ui64 operator()(const ui64 &x) const
    {
        static const ui64 r = rnd();
        return x ^ r;
    }
};
unordered_map<ui64, ui64, rnd_hash> t;
void _()
{
    int n = rd();
    ui64 ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        ui64 x = rd(), y = rd();
        ans += t[x] * i;
        t[x] = y;
    }
    cout << ans << '\n';
}
int main()
{
    int T = 1;
    // cin >> T;
    while (T--)
        _();
    return 0;
}

更优的哈希设计

在这片文章中,提到了一种更优秀的防卡哈希设计,即著名的 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);
    }
};

具体原理不需要了解,赛时直接看板子即可。

不过依旧很危险,不确定是我实现原因还是运气不好。

#include <bits/stdc++.h>
using namespace std;
using ui64 = unsigned long long;
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 unsigned long long rd()
{ // 读入一个 64 位无符号整数
    unsigned long long x = 0;
    char ch = gc();
    while (!isdigit(ch))
        ch = gc();
    while (isdigit(ch))
        x = x * 10 + (ch ^ 48), ch = gc();
    return x;
}
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);
    }
};
unordered_map<ui64, ui64, custom_hash> t;
void _()
{
    int n = rd();
    ui64 ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        ui64 x = rd(), y = rd();
        ans += t[x] * i;
        t[x] = y;
    }
    cout << ans << '\n';
}
int main()
{
    int T = 1;
    // cin >> T;
    while (T--)
        _();
    return 0;
}

hash_table

众所不一定周知,pbds 中有两个哈希表:

  • cc_hash_table
  • gp_hash_table

性能方面,gp 优于 cc,所以此处仅讨论 gp。

使用方法如下:

__gnu_pbds::gp_hash_table<ui64, ui64> t;

需要头文件 #include <bits/extc++.h>(C++ 拓展库的万能头)。

同样,在刻意制造哈希冲突的情况下,依旧会被卡。

由于本题中有专门针对 gp_hash_table 的数据,因此直接使用 gp_hash_table 同样不能通过本题。

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
using ui64 = unsigned long long;
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 unsigned long long rd()
{ // 读入一个 64 位无符号整数
    unsigned long long x = 0;
    char ch = gc();
    while (!isdigit(ch))
        ch = gc();
    while (isdigit(ch))
        x = x * 10 + (ch ^ 48), ch = gc();
    return x;
}
__gnu_pbds::gp_hash_table<ui64, ui64> t;
void _()
{
    int n = rd();
    ui64 ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        ui64 x = rd(), y = rd();
        ans += t[x] * i;
        t[x] = y;
    }
    cout << ans << '\n';
}
int main()
{
    int T = 1;
    // cin >> T;
    while (T--)
        _();
    return 0;
}

自定义哈希函数

不过对于上述提到的 splitmix64,在 gp_hash_table 这里就肥肠好使了。

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
using ui64 = unsigned long long;
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 unsigned long long rd()
{ // 读入一个 64 位无符号整数
    unsigned long long x = 0;
    char ch = gc();
    while (!isdigit(ch))
        ch = gc();
    while (isdigit(ch))
        x = x * 10 + (ch ^ 48), ch = gc();
    return x;
}
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);
    }
};
__gnu_pbds::gp_hash_table<ui64, ui64, custom_hash> t;
void _()
{
    int n = rd();
    ui64 ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        ui64 x = rd(), y = rd();
        ans += t[x] * i;
        t[x] = y;
    }
    cout << ans << '\n';
}
int main()
{
    int T = 1;
    // cin >> T;
    while (T--)
        _();
    return 0;
}

显然,就算有板子,抄下来这些奇奇怪怪的十六进制数也只一种折磨。

不过,比较友好的是,可以用三个随机数取代这三个奇怪的数,哈希表的性能也没有太大的影响。

#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
using ui64 = unsigned long long;
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 unsigned long long rd()
{ // 读入一个 64 位无符号整数
    unsigned long long x = 0;
    char ch = gc();
    while (!isdigit(ch))
        ch = gc();
    while (isdigit(ch))
        x = x * 10 + (ch ^ 48), ch = gc();
    return x;
}
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
struct rnd_hash
{
    ui64 operator()(ui64 x) const
    {
        static const ui64 s1 = rnd(), s2 = rnd(), s3 = rnd();
        x += s1;
        x = (x ^ (x >> 33)) * s2;
        x = (x ^ (x >> 30)) * s3;
        return x;
    }
};
__gnu_pbds::gp_hash_table<ui64, ui64, rnd_hash> t;
void _()
{
    int n = rd();
    ui64 ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        ui64 x = rd(), y = rd();
        ans += t[x] * i;
        t[x] = y;
    }
    cout << ans << '\n';
}
int main()
{
    int T = 1;
    // cin >> T;
    while (T--)
        _();
    return 0;
}
posted @ 2026-11-03 15:09  Zvelig1205  阅读(2)  评论(0)    收藏  举报