NOI2024 D1T1 - 集合 题解
观察
我们称 \(x\) 在一段序列中的“位置集合”为 \(x\) 出现的下标的集合。注意到,两段序列能够匹配,当且仅当两段序列的 \(1 \sim m\) 中的数的位置集合构成的多重集相等。快速比较集合,考虑哈希。
哈希
先实现一个从整数到整数的哈希 \(f(x)\)。使用这个哈希的目的是为了提高随机性,防止被卡。
再考虑两个哈希(都使用自然溢出):
-
\(H(S)\):把位置集合映射为一个整数。其中位置集合指的是数字 \(x\) 在集合序列 \(A\) 或 \(B\) 中出现的的下标的集合。
用 sum hash 实现: \(H(S)= \sum_{x \in S} f(x)\)。sum hash 的一个好处是,每次加入或删除位置集合中的一个元素,时间复杂度是 \(O(1)\)。这里直接对 \(x\) 求和(应该)也是可行的,但对 \(f(x)\) 求和能够提高随机性。
-
\(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;
}