异或哈希

前言

异或哈希是个很神奇的算法,利用了异或操作的特殊性和哈希降低冲突的原理,可以用于快速找到一个组合是否出现,序列中的数是否出现了 \(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;
}
posted @   Steven1013  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示