問題
整数ストリームに対する ADD x, FREQ x(誤差 $\varepsilon n$)、DISTINCT(相対誤差 0.1)を処理。
制約
$1 \le Q \le 2 \times 10^5$
$1 \le x \le 10^9$
$\varepsilon = 0.01, \delta = 0.01$
入出力例
入力例 1
8
ADD 1
ADD 2
ADD 1
ADD 3
ADD 1
FREQ 1
FREQ 2
DISTINCT
出力例 1
3
1
3
ヒント (段階的開示)
ヒント1: 方向性
Count-Min Sketch: $w \times d$ テーブル + universal hash。誤差確率 $\Pr[\hat f - f > \varepsilon n] \le \delta$。
ヒント2: アプローチ
HyperLogLog: ハッシュ下位 $b$ ビット = バケットID、残りの先頭0ビット数で $2^R$ を推定。調和平均で安定化。
ヒント3: 誘導
w = ceil(e/eps), d = ceil(ln(1/delta))。$\alpha_m \approx 0.7213/(1 + 1.079/m)$。模範解答 (Python)
import sys
import math
import random
input = sys.stdin.readline
class CountMinSketch:
def __init__(self, epsilon=0.01, delta=0.01):
self.w = max(1, math.ceil(math.e / epsilon))
self.d = max(1, math.ceil(math.log(1.0 / delta)))
self.table = [[0] * self.w for _ in range(self.d)]
p = (1 << 31) - 1
self.hashes = [(random.randint(1, p-1), random.randint(0, p-1), p) for _ in range(self.d)]
def add(self, x):
for i, (a, b, p) in enumerate(self.hashes):
self.table[i][((a*x+b)%p)%self.w] += 1
def query(self, x):
return min(self.table[i][((a*x+b)%p)%self.w] for i, (a,b,p) in enumerate(self.hashes))
class HyperLogLog:
def __init__(self, b=10):
self.b = b
self.m = 1 << b
self.M = [0] * self.m
if self.m == 16: self.alpha = 0.673
elif self.m == 32: self.alpha = 0.697
elif self.m == 64: self.alpha = 0.709
else: self.alpha = 0.7213 / (1 + 1.079 / self.m)
def _hash(self, x):
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9
x = (x ^ (x >> 27)) * 0x94d049bb133111eb
x = x ^ (x >> 31)
return x & ((1 << 64) - 1)
def add(self, x):
h = self._hash(x)
j = h & (self.m - 1)
w = h >> self.b
leading_zeros = 0
for bit in range(64 - self.b, 0, -1):
if w & (1 << (bit - 1)): break
leading_zeros += 1
rho = leading_zeros + 1
self.M[j] = max(self.M[j], rho)
def count(self):
Z = sum(2.0 ** (-m) for m in self.M)
E = self.alpha * self.m * self.m / Z
if E <= 2.5 * self.m:
V = self.M.count(0)
if V > 0:
E = self.m * math.log(self.m / V)
elif E > (1 << 32) / 30:
E = -(1 << 32) * math.log(1 - E / (1 << 32))
return round(E)
def solve():
cms = CountMinSketch(0.01, 0.01)
hll = HyperLogLog(10)
Q = int(input())
out = []
for _ in range(Q):
line = input().split()
if line[0] == "ADD":
x = int(line[1]); cms.add(x); hll.add(x)
elif line[0] == "FREQ":
out.append(str(cms.query(int(line[1]))))
elif line[0] == "DISTINCT":
out.append(str(hll.count()))
print('\n'.join(out))
solve()
Step-by-Step 解説
1Count-Min Sketch
行ごと独立 hash でカウントし、最小値を取る(オーバーカウントが各行独立)。
行ごと独立 hash でカウントし、最小値を取る(オーバーカウントが各行独立)。
2HyperLogLog
調和平均で $2^R$ 推定。バケット $m$ で分散させ相対誤差 $\approx 1.04/\sqrt m$。
調和平均で $2^R$ 推定。バケット $m$ で分散させ相対誤差 $\approx 1.04/\sqrt m$。
3定数と補正
小カーディナリティは Linear Counting、大は $2^{32}$ 補正。
小カーディナリティは Linear Counting、大は $2^{32}$ 補正。
4Universal Hash
$h(x) = (ax+b) \bmod p \bmod w$ で 2-universal。
$h(x) = (ax+b) \bmod p \bmod w$ で 2-universal。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| rho オフバイワン | 0ビット数 vs 1+0ビット数 | rho = leading_zeros + 1 |
| $\alpha_m$ 誤差 | m が小さい場合 | $m = 16, 32, 64$ は定数テーブル |
| delta の対数 | 底の混乱 | math.log は自然対数 |
次のステップ
- Count Sketch (+/- カウンタ)
- Second Moment 推定 (JL)