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. 为每个题号 1 ~ n 生成一个随机 64 位数
  2. 遍历 i = 1 ~ n:
    • 把 A[i] 的哈希值异或进 a
    • 把 B[i] 的哈希值异或进 b
    • 把 C[i] 的哈希值异或进 c
    • 如果 a == b || a == c || b == c,答案加一
  3. 输出答案

三、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,避免溢出问题

六、相关题目

七、总结

要点 内容
核心观察 某成员支持分界线 ⇔ 他排名前 i 的集合 = 左侧集合
转化 判断三个排列的前 i 项集合是否有两个相等
技巧 用随机哈希 + 异或和快速比较集合
复杂度 O(n)
posted @ 2026-09-29 11:24  leaderling  阅读(7)  评论(0)    收藏  举报