問題
$N$ 個の整数集合 $S_1,\dots,S_N$ と、整数 $b,r$、しきい値 $\theta=p/q$ が与えられる。$k=b\cdot r$ とし、$P=2^{61}-1$ として $i=0,\dots,k-1$ のハッシュ関数を
$$h_i(x)=(a_i x+c_i)\bmod P,\quad a_i=1+(\mathrm{sm}(2i+1)\bmod (P-1)),\quad c_i=\mathrm{sm}(2i+2)\bmod P$$
で定める($\mathrm{sm}$ は splitmix64 の finalizer。Day159-Q4 と同じ関数)。
1. 署名: $\mathrm{sig}_j[i]=\min_{x\in S_j}h_i(x)$(長さ $k$)
2. LSH: 署名を長さ $r$ の $b$ 本の帯に切る。ある帯 $t$ で $\mathrm{sig}_u[tr..tr+r-1]=\mathrm{sig}_v[tr..tr+r-1]$ となる組 $u<v$ を 候補 とする
3. 検証: 候補のうち厳密な Jaccard 係数 $J(S_u,S_v)=\frac{|S_u\cap S_v|}{|S_u\cup S_v|}\ge\theta$ を満たすものを数える
候補の組数 $C$ と、検証を通った組数 $D$ を出力せよ。
入力形式
N b r p q
m_1 e_{1,1} ... e_{1,m_1}
...
m_N e_{N,1} ... e_{N,m_N}
制約
| # | 条件 |
|---|---|
| 1 | $2\le N\le 10^4$、$1\le b\cdot r\le 100$、$0\le p\le q\le 100$、$q\ge1$ |
| 2 | $1\le m_j$、$\sum m_j\le 2\times10^5$、$0\le e<10^9$、各行の要素は相異なる |
| 3 | 候補の組数 $C\le 2\times10^5$ が保証される |
入出力例
入力例1
5 4 2 1 2
6 1 2 3 4 5 6
6 1 2 3 4 5 7
4 1 2 3 8
3 100 200 300
3 100 200 301出力例1
3 1(厳密な Jaccard は $(1,2)$: 5/7=0.714、$(1,3),(2,3)$: 3/7=0.429、$(4,5)$: 2/4=0.5。$k=8$ 本の最小値の一致は $(1,2)$ が 7/8、$(1,3)$ が 4/8、$(4,5)$ が 2/8。帯(2 本ずつ 4 帯)が丸ごと一致したのは $(1,2)$ の 3 帯と $(1,3),(2,3)$ の 1 帯 → 候補 3 組。検証で $(1,3),(2,3)$ は落ち $D=1$。**$(4,5)$ は $J=0.5\ge\theta$ なのに候補にならなかった(偽陰性)**——これが LSH の代償)
MinHash は Broder「On the resemblance and containment of documents」(SEQUENCES 1997)で AltaVista の近似重複検出に使われた手法、帯分割は Leskovec・Rajaraman・Ullman『Mining of Massive Datasets』3 章(http://www.mmds.org/ 、2026-09-25 参照)の定式化に従う。
概念図:LSH の S 字曲線(理論と実測)
ヒント(クリックで段階的に開示)
ヒント1(方向性)
一様ランダムな置換 $\pi$ で $\min\pi(A)=\min\pi(B)$ となる確率は、ちょうど $J(A,B)$($A\cup B$ の中で $\pi$ が最小の要素が $A\cap B$ に入る確率)。$k$ 本の最小値の一致率は $J$ の不偏推定で、標準誤差 $\sqrt{J(1-J)/k}$。
ヒント2(アプローチ)
署名を全ペアで比べると $O(N^2k)$。帯 $t$ ごとに「$r$ 個の値の組」をキーに辞書へ入れれば、同じバケツの組だけが候補になる。1 組が候補になる確率は $1-(1-J^r)^b$ の S 字曲線で、しきい値はおよそ $(1/b)^{1/r}$。
ヒント3(誘導)
sig = [[min((a * x + c) % P for x in S) for a, c in zip(A, C)] for S in sets]
cand = set()
for t in range(b):
buckets = {}
for j in range(n):
buckets.setdefault(tuple(sig[j][t*r:(t+1)*r]), []).append(j)
for g in buckets.values():
... # g の中の全組を cand に(帯をまたいだ重複は set で消える)
# 検証: q * |A∩B| >= p * |A∪B|(浮動小数を使わない)模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
P = (1 << 61) - 1
MASK = (1 << 64) - 1
def splitmix64(x):
x = (x + 0x9E3779B97F4A7C15) & MASK
x = ((x ^ (x >> 30)) * 0xBF58476D1CE4E5B9) & MASK
x = ((x ^ (x >> 27)) * 0x94D049BB133111EB) & MASK
return x ^ (x >> 31)
def main():
data = sys.stdin.buffer.read().split()
n, b, r, p, q = map(int, data[:5])
k = b * r
A = [1 + splitmix64(2 * i + 1) % (P - 1) for i in range(k)]
C = [splitmix64(2 * i + 2) % P for i in range(k)]
sets = []
pos = 5
for _ in range(n):
m = int(data[pos]); pos += 1
sets.append(set(map(int, data[pos:pos + m]))); pos += m
# 1) MinHash 署名: sig[i] = min_x (A[i]*x + C[i]) mod P
sigs = []
for S in sets:
sigs.append([min((a * x + c) % P for x in S) for a, c in zip(A, C)])
# 2) LSH: 帯 t の r 個をキーにバケツへ。同じバケツの組が候補
cand = set()
for t in range(b):
buckets = {}
for j in range(n):
key = tuple(sigs[j][t * r:(t + 1) * r])
buckets.setdefault(key, []).append(j)
for g in buckets.values():
for x in range(len(g)):
for y in range(x + 1, len(g)):
cand.add((g[x], g[y]))
# 3) 候補だけ厳密な Jaccard で検証: |A∩B|/|A∪B| >= p/q <=> q*|A∩B| >= p*|A∪B|
ok = 0
for u, v in cand:
inter = len(sets[u] & sets[v])
union = len(sets[u]) + len(sets[v]) - inter
if q * inter >= p * union:
ok += 1
print(len(cand), ok)
main()
計算量: 署名 $O(k\sum m_j)$、LSH $O(bN + C)$、検証 $O(\sum_{\text{候補}}\min(|S_u|,|S_v|))$。全ペア厳密計算の $O(N^2\cdot\bar m)$ と違い、**$N$ について線形**(候補数が小さい限り)。
Step-by-Step 解説
$A\cup B$ の各要素が置換で「最小」になる確率は等しい。最小の要素が $A\cap B$ にあれば両方の最小値が一致し、$A\setminus B$ か $B\setminus A$ にあれば一致しない。よって $\Pr=|A\cap B|/|A\cup B|$。本問は真の置換の代わりに $(ax+c)\bmod P$ を使う。これは min-wise independent ではない(近似)が、実測で偏りは $\pm0.005$ 以内($k=100$, 各 200 組)。
一致数は二項分布 $\mathrm{Bin}(k,J)$。実測 RMSE($k=100$): $J=0.2$ で 0.0377(理論 0.0400)、$J=0.5$ で 0.0479(0.0500)、$J=0.8$ で 0.0384(0.0400)。**$k=100$ では $J$ を $\pm0.05$ 程度でしか当てられない**ので、最終判定は厳密計算で行う(本問の手順 3)。
帯 1 本が丸ごと一致する確率 $J^r$、$b$ 本のどれかが一致する確率 $1-(1-J^r)^b$。$r$ を増やすと曲線が右(厳しく)、$b$ を増やすと左(緩く)に動く。$b=20,r=5$ の実測(各 300 組): $J=0.3$ で 0.060(理論 0.047)、$0.5$ で 0.487(0.470)、$0.7$ で 0.977(0.975)。しきい値の目安 $(1/20)^{1/5}=0.549$。
近似重複データ(1000 クラスタ × 10 件、各コピーは元の 20 要素のうち 2 個を入れ替え、真に $J\ge0.5$ の組 45,000)での実測:
| $(b,r)$ | しきい値 $(1/b)^{1/r}$ | 候補 $C$ | 真の組の再現率 | 時間 |
|---|---|---|---|---|
| (10, 10) | 0.79 | 9,331 | 20.7% | 3.91 s |
| (20, 5) | 0.55 | 42,868 | 95.3% | 3.91 s |
| (25, 4) | 0.45 | 44,846 | 99.7% | 4.09 s |
| 全ペア厳密 | — | 5×10^7 組 | 100% | 27.68 s |
目的のしきい値 $\theta$ より 曲線の中心を少し左に置く($b$ を増やす)と見逃しが減り、増えた偽陽性は手順 3 の検証で落とせる。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 全ペア厳密 Jaccard | $O(N^2\bar m)$ | 比較用 | $N=10^4$ で 27.7 s(実測)。$N=2000$ では 1.13 s と LSH(0.75 s)に近く、**$N$ が数千までなら全ペアで十分** |
| 転置索引 + 共通要素数カウント(prefix filtering, AllPairs) | 出力依存 | 扱わない | 厳密で偽陰性 0。しきい値が高く集合が小さいなら有力。本問は LSH の確率的トレードオフを学ぶため除外 |
| MinHash + 帯分割 LSH(本解) | $O(k\sum m+C)$ | 採用 | 再現率を $(b,r)$ で連続的に調整できる |
| 1 本の MinHash($k$ 個の置換ではなく bottom-$k$ スケッチ) | $O(\sum m\log k)$ | 扱わない | ハッシュ 1 本で済み速いが、帯分割とは組み合わせにくい |
| SimHash(Charikar 2002、コサイン類似度) | $O(\sum m)$ | 不採用 | 類似度の定義が Jaccard ではない。重み付き特徴向け |
前提が変わったら: 偽陰性が許されない(法務の重複調査など)なら prefix filtering などの厳密法。データが 10^8 件なら署名を分散計算し帯ごとにシャッフルする(MapReduce)形が MMDS の標準構成。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 帯ごとに見つけた組を足し合わせる | 複数の帯で一致した組を重複して数える | set で組を 1 回だけ数える |
| Jaccard を浮動小数で比べる | $5/7\ge0.5$ は安全でも、境界 $J=\theta$ で誤差が出る | q * inter >= p * union と整数で比較 |
| 署名の値を $\bmod 2^{32}$ に縮める | 異なる最小値が衝突し、一致率が $J$ より大きくなる | $P=2^{61}-1$ のまま比べる |
| $h_i(x)=(a_ix+c_i)\bmod P$ で $a_i=0$ を許す | 全要素が同じ値になり常に一致 | $a_i\in[1,P-1]$ |
| 最小値ではなく「最小値を与える要素」を比べる | 同じ意味だが、ハッシュ値の方が比較が安い | どちらでもよいが、本問は値で定義 |
次のステップ
- 発展1: 帯のしきい値を $\theta$ に合わせる $(b,r)$ を、偽陽性の検証コストと偽陰性の損失の重み付き和が最小になるように選ぶ(MMDS 3.4.3)
- 発展2: 文書を文字 $k$-gram(shingle)に変換してから本問に流す。$k=5$ と $k=9$ で近似重複の検出率を比べる
- 扱わなかった範囲: 重み付き Jaccard(ICWS)、b-bit MinHash による署名の圧縮、LSH Forest
検証ログ(本セッションで実行した数値)
- 入力例1: 最小値の一致ベクトル $(1,2)$=
[1,1,1,1,0,1,1,1](帯一致 T,T,F,T)、$(1,3)$=[1,0,1,1,0,1,0,0](帯 2 のみ)、$(4,5)$=[0,1,0,0,1,0,0,0](帯一致なし)。出力3 1 - 素朴な全ペア帯比較との一致(実測): ランダム 200 ケース($N\le15$、要素の値域 15 以下で衝突多め)で不一致 0
- 推定誤差(実測, $k=100$, $|A|=|B|=100$, 各 200 組): 上の Step 2 のとおり、平均の偏り $+0.0002/-0.0051/+0.0052$
- S 字曲線(実測, $b=20, r=5$, 各 300 組): $J=0.2$: 0.003(0.006)、0.4: 0.197(0.186)、0.6: 0.787(0.802)、0.8: 1.000(1.000)
- 規模(実測): $N=2000$($\sum m=4\times10^4$)で LSH 0.75 s・再現率 8590/9000=95.4% vs 全ペア 1.13 s。$N=10^4$ は Step 4 の表
- 出所ラベル: 誤差・曲線・再現率・秒数は実測(Python 3.14.7)。$\Pr=J$ と $1-(1-J^r)^b$ は Broder 1997・MMDS 3 章の導出。AllPairs・SimHash・ICWS は文献上の比較で本セッションでは実装していない
- 検証スクリプト: スクラッチパッドの
d160/q2_test.py・q2_test2.py・q2_test3.py