NOI2024 D1T1 - 集合 题解

观察

我们称 \(x\) 在一段序列中的“位置集合”为 \(x\) 出现的下标的集合。注意到,两段序列能够匹配,当且仅当两段序列的 \(1 \sim m\) 中的数的位置集合构成的多重集相等。快速比较集合,考虑哈希。

哈希

先实现一个从整数到整数的哈希 \(f(x)\)。使用这个哈希的目的是为了提高随机性,防止被卡。

再考虑两个哈希(都使用自然溢出):

  1. \(H(S)\):把位置集合映射为一个整数。其中位置集合指的是数字 \(x\) 在集合序列 \(A\)\(B\) 中出现的的下标的集合。

    用 sum hash 实现: \(H(S)= \sum_{x \in S} f(x)\)。sum hash 的一个好处是,每次加入或删除位置集合中的一个元素,时间复杂度是 \(O(1)\)。这里直接对 \(x\) 求和(应该)也是可行的,但对 \(f(x)\) 求和能够提高随机性。

  2. \(G(T)\):把整数的多重集 \(T\) 映射为一个整数。(这里的多重集指的就是位置集合的哈希值构成的集合。)

    仍然使用 sum hash:\(G(T) = \sum_{x \in T} f(x)\)。为了保险,可以再设计一个平方和的哈希:\(G'(T) = \sum_{x \in T} f^{2}(x)\)

(1 和 2 实际上可以看作一个哈希:都是集合到整数的映射。)

一个错误示范:

1 和 2 中的两个整数哈希函数 \(f(x)\) 可以是不同的。1 中 \(x\) 的值域为 \(1 \sim n\),我们可以先用 mt19937_64\(1 \sim n\) 中的所有整数赋一个随机的权值,作为 \(f(x)\)。2 中 \(x\) 的值域太大,不能预处理,可以用 xor shift 实现哈希:

u64 Hash(u64 x)
{
    x ^= mask, x ^= x << 13, x ^= x >> 7, x ^= mask;
    return x;
}

其中 mask 是一个随机的常数,而左移右移的位数和次数是随便选的。

注:集合的哈希函数用 xor sum 似乎错误率更高,不知道为什么,参见这个提交

好吧我知道为什么了,我怎么这么蠢😅由于两个相同的数异或会抵消,加上集合是多重集,所以只要两个集合中元素出现次数的奇偶性相同,哈希值就相同。例如 \(G(\{x\}) = G(\{x, x, x\})\),即使异或的时候给 \(x\) 又套了一个哈希也完全没用。因此多重集哈希千万不要用 xor sum。

整数的哈希函数最好不要用多项式(why?),除此之外别的什么应该都可以,例如这个:

u64 Hash(u64 x)
{
    return (x * x) ^ (x * x * x - x);
}

代码

//  P10785 [NOI2024] 集合
#include<bits/stdc++.h>
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define sqr(x) ((x) * (x))

using namespace std;

typedef array<int, 3> Set;
typedef unsigned long long u64;

const u64 mask = mt19937_64(chrono::system_clock().now().time_since_epoch().count())();

int n, m, q;
vector<Set> A, B;

vector<u64> Apos, Bpos; // multiple set of numbers' positions -> u64
u64 Ahsh, Bhsh, Ahsh2, Bhsh2; // set -> u64
vector<u64> pw;
vector<int> ans;
vector<u64> val;
mt19937_64 rng(chrono::system_clock().now().time_since_epoch().count());

struct Query
{
	int r, id;
	Query(int _r, int _id): r(_r), id(_id) {}
};
vector<vector<Query>> que;

void init()
{
	val.resize(n + 1);
	for(int i = 1; i <= n; i++)
		val[i] = rng();
}

u64 Hash(u64 x)
{
	x ^= mask, x ^= x << 13, x ^= x >> 7, x ^= mask;
	return x;
}

void modify(int pos, int t)
{
	if(pos == 0 || pos == n + 1) return;
	for(int x: A[pos])
	{
		u64 H = Hash(Apos[x]);
		Ahsh -= H, Ahsh2 -= sqr(H), Apos[x] += t * val[pos];
		u64 H2 = Hash(Apos[x]);
		Ahsh += H2, Ahsh2 += sqr(H2);
	}
	for(int x: B[pos])
	{
		u64 H = Hash(Bpos[x]);
		Bhsh -= H, Bhsh2 -= sqr(H), Bpos[x] += t * val[pos];
		u64 H2 = Hash(Bpos[x]);
		Bhsh += H2, Bhsh2 += sqr(H2);
	}
}

void add(int pos) {modify(pos, 1);}
void del(int pos) {modify(pos, -1);}

bool check() {return Ahsh == Bhsh && Ahsh2 == Bhsh2;}

int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> m >> q;
	init();
	A.resize(n + 1), B.resize(n + 1);
	for(int i = 1; i <= n; i++)
		for(int &x: A[i]) cin >> x;
	for(int i = 1; i <= n; i++)
		for(int &x: B[i]) cin >> x;
	que.resize(n + 1);
	for(int i = 1, l, r; i <= q; i++)
	{
		cin >> l >> r;
		que[l].emplace_back(r, i);
	}

	Apos.resize(m + 1), Bpos.resize(m + 1);

	ans.resize(q + 1), add(1);
	for(int l = 1, r = 1; l <= n; l++)
	{
		del(l - 1);
		if(r < l) assert(r == l - 1), add(++r);
		while(r <= n && check()) add(++r);
		del(r--);
		// debug("l = %d, r = %d\n", l, r);
		assert(check()), assert(r >= l);
		for(auto nd: que[l])
			if(r >= nd.r) ans[nd.id] = 1;
	}

	for(int i = 1; i <= q; i++)
		cout << (ans[i] ? "Yes" : "No") << endl;

	return 0;
}

提交记录

posted @ 2024-10-21 19:43  DengStar  阅读(18)  评论(0编辑  收藏  举报