EPS.AC【哈希模板】
难度分层(Difficulty Tiers)
一、题目描述
EPS.AC 队有三名成员 Colin、Claris、Little_Sheep_Yawn,他们各自把 n 道题按从易到难排序(给出三个 1 ~ n 的排列)。
现在要把这 n 道题划分成若干个难度层,从易到难排列:
- 每道题恰好属于一层
- 每层至少包含一道题
- 同一层内部不区分难度
对于相邻两层之间的分界线,定义:
若某成员眼中,「左侧所有题」都排在「右侧所有题」之前,则他支持这条分界线。
一条分界线合法,当且仅当至少有两名成员支持它。
问:最多能划分出多少层?
对应题目:EPS.AC
二、思路分析
关键观察
把每个排列看成一个序列。假设我们在某个位置 i 处切一刀(即前 i 道题属于左边的层,后 n - i 道题属于右边的层)。
对于某个成员,他支持这条分界线,当且仅当:
他排名前
i的题目集合,恰好等于左边所有题的集合。
换句话说,如果某成员的排列中,前 i 个位置正好是「左边那一组题」,那么他就支持这条分界线。
转化问题
对于位置 i,我们考虑三个排列各自的前 i 个元素组成的集合:
- 集合
SA:Colin 的前i个题 - 集合
SB:Claris 的前i个题 - 集合
SC:Little_Sheep_Yawn 的前i个题
如果其中至少两个集合相等,那么这条分界线就至少有两人支持,合法。
例如 SA == SB,说明 Colin 和 Claris 都认为前 i 个题是同一组,两人都支持这条分界线。
如何快速判断集合相等
用哈希:给每个题目 i 分配一个随机数 h[i],用集合内所有题目的哈希值异或和代表这个集合。
- 集合相等 → 异或和相等(大概率)
- 集合不等 → 异或和大概率不等
这样每次只需比较三个异或和是否两两相等。
算法流程
- 为每个题号
1 ~ n生成一个随机 64 位数 - 遍历
i = 1 ~ n:- 把
A[i]的哈希值异或进a - 把
B[i]的哈希值异或进b - 把
C[i]的哈希值异或进c - 如果
a == b || a == c || b == c,答案加一
- 把
- 输出答案
三、AC 代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int A[N], B[N], C[N];
typedef unsigned long long ull;
static inline ull splitmix64(ull x)
{
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
ull seed = (ull)chrono::steady_clock::now().time_since_epoch().count();
seed = splitmix64(seed);
while (T--)
{
int n;
cin >> n;
vector<ull> vec(n + 1);
for (int i = 1; i <= n; i++)
vec[i] = splitmix64(seed + (ull)i);
for (int i = 1; i <= n; i++) cin >> A[i];
for (int i = 1; i <= n; i++) cin >> B[i];
for (int i = 1; i <= n; i++) cin >> C[i];
ull a = 0, b = 0, c = 0;
int res = 0;
for (int i = 1; i <= n; i++)
{
a ^= vec[A[i]];
b ^= vec[B[i]];
c ^= vec[C[i]];
if (a == b || a == c || b == c) res++;
}
cout << res << endl;
}
return 0;
}
四、代码说明
1. 随机哈希函数
static inline ull splitmix64(ull x)
{
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
用 splitmix64 生成高质量的 64 位伪随机数。相比 rand(),它分布均匀、速度快、碰撞概率极低。
2. 为每个题号分配哈希值
vector<ull> vec(n + 1);
for (int i = 1; i <= n; i++)
vec[i] = splitmix64(seed + (ull)i);
每个题号 1 ~ n 对应一个唯一的随机哈希值。
3. 异或和维护集合
a ^= vec[A[i]];
b ^= vec[B[i]];
c ^= vec[C[i]];
用异或和代表集合:
- 集合相同 → 异或和相同
- 集合不同 → 异或和大概率不同
异或的好处:加入和删除同一个元素会相互抵消,天然适合动态维护集合。
4. 判断分界线是否合法
if (a == b || a == c || b == c) res++;
只要三人中有两人的集合相等,这条分界线就合法。
5. 随机种子
ull seed = (ull)chrono::steady_clock::now().time_since_epoch().count();
seed = splitmix64(seed);
用当前时间作为种子,保证每次运行哈希值不同,防止被特殊数据卡掉。
五、易错点
- 哈希函数用 splitmix64,不要用
rand(),质量不够容易碰撞 - 异或和要用
unsigned long long,避免溢出问题
六、相关题目
- 本题 — 难度分层
- Codeforces 哈希题合集 — 练习哈希技巧
- 洛谷 P3370 字符串哈希 — 哈希基础
七、总结
| 要点 | 内容 |
|---|---|
| 核心观察 | 某成员支持分界线 ⇔ 他排名前 i 的集合 = 左侧集合 |
| 转化 | 判断三个排列的前 i 项集合是否有两个相等 |
| 技巧 | 用随机哈希 + 异或和快速比较集合 |
| 复杂度 | O(n) |

浙公网安备 33010602011771号