競プロ解説 [ABC250E]
はじめに
本記事は、日々の精進で出会った問題の一部を丁寧目に解説することで、筆者と読者のアルゴリズム力を向上させることを目的としています。
問題
解説
本問題は集合の比較に関する問題です。
Zobrist hashing
- 集合の各要素に乱数を割り振る
- 要素に対応する乱数のXORをその部分集合のハッシュ値とする
本問題では u32
の要素を改めて u32
の乱数に置き換えています。これはムダな操作ではありません。次の例を考えてみましょう。
集合
を考えます。部分集合 S = \{ 0, 1, 2, 3, 4, 5, 6, 7 \} に XOR hashing を施すと、いずれも \{ 0, 7 \}, \{ 1, 6 \}, \{ 2, 5 \}, \{ 3, 4 \} になってしまいます。 7
このようにある種の入力に対してXOR hashingは脆弱です。XOR hashingを安全に行うために必要な条件を明らかにするために、もう少し考察を進めます。
まず、連続値に弱いことは明らかです。さらに値の範囲が狭いことも問題です。たとえば u32
の範囲で)疎らに割り振ることでこうした問題を解決しています。
最後に衝突確立を考えます[1]。考慮する部分集合の数が
ここで、ネイピア数の定義[2]を用いています。本問の制約では u64
にとると、衝突する確率は
回答例
use proconio::{fastout, input, marker::Usize1};
use rand::RngCore;
use rustc_hash::{FxHashMap, FxHashSet};
#[fastout]
fn main() {
let (acc_hashed_a, acc_hashed_b) = {
input! { n: usize, a: [u32; n], b: [u32; n], }
let mut rng = rand::thread_rng();
let hash_dict = FxHashMap::from_iter(
// iter.sort_unstable().dedup() や iter.unique() より高速
// itertools の unique() は標準ライブラリの HashMap を用いている
FxHashSet::from_iter(a.iter().chain(b.iter()))
.into_iter()
.map(|v| (*v, rng.next_u32())),
);
let acc_hash = |src: Vec<u32>| {
let mut res = Vec::with_capacity(src.len());
let mut used = FxHashSet::with_capacity_and_hasher(
src.len(), Default::default()
);
let mut hash = 0;
for elem in src {
if used.insert(elem) {
hash ^= hash_dict[&elem]
}
res.push(hash)
}
res
};
(acc_hash(a), acc_hash(b))
};
input! { q: usize, }
for _ in 0..q {
input! { x: Usize1, y: Usize1, }
if acc_hashed_a[x] == acc_hashed_b[y] {
println!("Yes")
} else {
println!("No")
}
}
}
Discussion