本题使用较为稳定的伪随机数生成 (mt19937生成器),所以生成的数据是比较均匀的伪随机数。
不同哈希策略的比较(仅供参考):
设哈希函数为 H(x)=x\bmod len ,即除留余数法
使用开放定址法,并使用线性探测法 key=key+d_i\bmod m ,设 d_i=2333 。
一:实践检验 len 的影响
设 len 为 10^7 ,冲突率过高, TLE
设 len 为 2\times 10^7 或其附近的素数,或更大的数字,用时均为 4.2s
二:实践检验 d_i 的影响
len 固定为 2\times10^7+3 ,已知 d_i=2333 ( 2333 是质数)时表现为 4.2s
更改 d_i 为 1 , TLE
更改 d_i 为 2332 ,用时为 4.1s
更改 d_i 为 1,2,3,\cdots , TLE
更改线性探测法为幂次方探测法:
更改 d_i 为 1^2,2^2,3^2,\cdots ,用时为 2.8s
更改 d_i 为 1^3,2^3,3^3,\dots ,用时为 1.3s
更改 d_i 为 1^4,2^4,3^4,\dots ,用时为 0.985s
继续提升 d_i 为 1^5,2^5,3^5,\cdots 等继续升幂,没有明显改进,甚至可能变慢。
更改 d_i 为 1^2,-1^2,2^2,-2^2,\cdots ,用时为 0.76s (二次探测法)
更改 d_i 为 1^3,-1^3,2^3,-2^3,\cdots ,用时没有改善
三:实践检验拉链法
使用 vector 拉链,用时为 2.5s ,空间开销自带几倍常数 (约 450MB )
使用 set 拉链,用时为 3.3s ,空间开销是十多倍常数(接近 1GB)
使用 unordered_set 拉链,用时同上,但直接 MLE
使用 deque 拉链,直接 RE 和几乎 MLE ,实践表明约 7.5\times 10^5 个 deque 就直接 500MB 了
使用 pb_ds
的红黑树,用时约 2.7s , MLE
使用 pb_ds
的splay树,用时约 2.7s , MLE
使用 pb_ds
的有序向量树,用时约 3.5s ,约 900MB
综上所述,实践表明:对随机数据和一倍空间冗余的除留余数法哈希函数,开放定址最优,最优冲突处理策略是二次探测法。
参考代码:(一倍空间冗余的除留余数法+开放定址法+二次探测法)
#include <bits/stdc++.h>
using namespace std;
#define mn 20000003
int n, a[mn], seed, m, ans;
signed main()
{
scanf("%d%d%d", &n, &m, &seed);
mt19937 mt(seed);
uniform_int_distribution<int> dist(0, m);
for (int i = 1; i <= n; ++i)
{
int v = dist(mt) + 1; //防止哈希值为0跟空重复,使其值域为[1,m+1]
int hi = v % mn;
bool repeat = a[hi] == v;
int dt = 1, dv = 1;
while (a[hi] != 0)
{
hi = (hi + dv * dt * dt) % mn;
dv = -1 * dv;
if (dv == 1)
{
++dt;
}
if (a[hi] == v)
{
repeat = true;
break;
}
}
if (!repeat)
{
++ans;
a[hi] = v;
}
}
printf("%d", ans);
return 0;
}
参考代码:(除留余数法+vector拉链法)
#include <bits/stdc++.h>
using namespace std;
#define mn 10000010
int n, seed, m, ans;
vector<int> a[mn];
signed main()
{
scanf("%d%d%d", &n, &m, &seed);
mt19937 mt(seed);
uniform_int_distribution<int> dist(0, m);
for (int i = 1; i <= n; ++i)
{
int v = dist(mt);
int hi = v % mn;
bool repeat = false;
for (auto &j : a[hi])
{
if (j == v)
{
repeat = true;
break;
}
}
if (!repeat)
{
++ans;
a[hi].emplace_back(v);
}
}
printf("%d", ans);
return 0;
}
附:从理论而言,散列表的装填因子load factor,设 \alpha=\dfrac nm ,平均查找长度是 a 的函数,且有:
处理冲突的方法\平均查找长度 查找成功时 查找失败时 线性探测法 \dfrac12\left(1+\dfrac1{1-\alpha}\right) \dfrac12\left(1+\dfrac1{(1-\alpha)^2}\right) 二次探测法 -\dfrac1\alpha\ln(1+\alpha) \dfrac1{1-\alpha} 拉链法 1+\dfrac\alpha2 \alpha+e^{-\alpha}
为获得空间效率,建议保持 \alpha > 0.5 ,至少是半满的,一般上限设为 0.9