异或哈希
前言
异或哈希是个很神奇的算法,利用了异或操作的特殊性和哈希降低冲突的原理,可以用于快速找到一个组合是否出现,序列中的数是否出现了 \(k\) 次.
异或(xor)
含义: 数的二进制表示按位相加并对2取余
举个例子, \(3 \oplus 5 = (011)_2 \oplus (101)_2 = (110)_2 = 6\).
异或运算满足交换律,即: $A \oplus B = B \oplus A $.
此外异或运算相较于其他运算,有一个独有的特性: \(A \oplus 0 = A, A \oplus A = 0\)
$ \rightarrow $ 一个数对自身进行偶数次运算后等于 \(0\),进行奇数次运算后等于自身.
哈希(hashing)
哈希算法是一种用于快速定位资源的算法.
在C++中,我们常使用 \(unordered\_map\) (或者 \(pb\_ds\) 库内的哈希表)实现
异或哈希
- 思想
我们关注一个区间内出现了什么数字.
因此, 我们对每一个数字赋一个随机权值,
然后对这个权值进行一系列操作,例如前缀 \(\operatorname{xor}\) 等.
对于两个序列, 通过 Hash 的方式判断即可.
同时, 也可用于满足某些条件的子序列数量的问题.
我们可以通过 Hash 的方式找到前面满足某些条件的数, 来匹配子序列.
参考: https://www.cnblogs.com/kdlyh/p/18333737
先介绍正常扫描线做法: 按顺序枚举删去的边, 然后发现只要确定删去的一条边, 所有路径的选法都确定了, 再加上只要按顺序每条路径方向只变化两次, 故可以用线段树维护区间加减 \(0\) 个数.
首先设 \(n=6,m=2\), 且 \((1,3),(4,6)\) 是好朋友, 图中用紫线表示
对于每对朋友, 要么是通过优弧联通, 要么是用劣弧联通, 我们对优劣弧都染一下色
其中绿色/橙色是 \((4,6)\) 的劣弧/优弧, 蓝色/黄色是 \((1,3)\) 的劣弧/优弧.
要维护最少的路, 就是通过我们对于每对朋友都选择他们的劣弧/优弧后使得没有被染色的道路最多.
就有一个经典的思路: 保留最少 \(\leftrightarrow\) 删除最多.
我们分别对上面颜色的曲线进行编号:绿色是1,黄色是2,橙色是3,蓝色是4.
那么我们能选择的弧的集合其实是 $ (1,3),(2,3),(1,4),(2,4) $
其实就是我们要对每对朋友都选择一个弧,使得仅被这些弧染色的道路尽可能多,然后删除这些道路.
接下来就是实现了.
定义 \(edge_i\) 为 \(i \rightarrow i+1\) 的这条边, 例如 \(edge_1\) 就是 \(1\) 连向 \(2\) 的道路.
我们对每对朋友的两个端点都 \(\oplus rand\), 其中 \(rand\) 是一个六十四位的随机数, 即对于 \((1,3)\) 有 \(edge_1 \oplus rand,edge_3 \oplus rand\), 其中 \(rand\) 仅在这里是相同的, 即每对朋友在异或时的 \(rand\) 互不相同.
然后我们维护一个前缀和就可以得到 \(i \rightarrow i+1\) 这条路的染色情况了.
而这是非常抽象的,我们是怎么得到染色情况的呢?并且我们不是只染了一个弧吗,另一个难道直接不管了?
首先我们先简化模型,假设只有 \((1,3)\) 这一对朋友,并且我们恰好得到 \(rand=1\),那么有
然后又加上 \((4,6)\) 这对朋友, 并且 \(rand\) 恰好是 \(2\).
可以发现神奇的每个数值刚好都对应这一种弧的集合.
我们对两端都异或同一个随机数是通过差分的思想来 \(\mathcal{O}(1)\) 染色, 这样可以通过前缀和得知当前的染色情况.
可以通过前缀和得知染色情况是因为,我们通过六十四位的随机数异或值实现了哈希的思想,对于每种弧都有特定的哈希值,而弧集的哈希值是可以通过异或得到,这个比较抽象,所以建议可以理解为状压差不多的思想.
还有一个问题:为什么只对一个弧染色就相当于对两个弧都染色了呢
因为是异或的随机值,我们对优弧染上了 \(x\), 我们对优弧染上了 \(x\), 对劣弧染上了 \(y\),然后整个圈都同事异或 \(y\),相当于优弧染上了 \(x \oplus y\), 劣弧染了 \(0\), 因为是随机的异或值, 所以 \(x \oplus y\) 可以直接相当于 \(x\).
然后用统计下前缀和出现最多的数值,删除这个数就是答案.
#include "bits/stdc++.h"
using namespace std;
typedef unsigned long long ull;
const int N = 2e5+10;
int n, m;
ull a[N];
unordered_map<ull, int> t;
inline ull get_rand() {
return 65536ull * rand() * 65536 * 65536 + 65536ull * rand() * 65536 + 65536ull * rand() + rand();
}
int main() {
mt19937_64 rnd(time(0));
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
ull r = get_rand();
a[x] ^= r, a[y] ^= r;
}
for (int i = 1; i <= n; ++i) a[i] ^= a[i - 1], t[a[i]]++;
int ans = 0;
for (auto x : t) ans = max(ans, x.second);
cout << n - ans << endl;
for (int i = 1; i <= n + 1; ++i) a[i] = 0;
t.clear();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现