程序设计实习
最大割
最朴素的随机:随机给每个点以 \(\frac{1}{2}\) 概率分到哪边,期望得到 \(\frac{m}{2}\) 条边。
- 当然实际上我们有确定性能分出去至少 \(\frac{m}{2}\) 条边的算法。
我们有 Markov Inequality:对于随机变量 \(X\),且一定有 \(X\ge 0\),则 \(\text{Pr}[X\ge a]\le \frac{\mathbb{E}[X]}{a}\)
那么对于这个随机算法,考虑随机变量 \(X=m-|\text{cut}(S)|\),则 \(\mathbb E[X]=\frac{m}{2}\);对于 \(\varepsilon>0\),有
例如取 \(\varepsilon=0.05\),即我们需要找到一个至少 \(0.45m\) 条边的最大割,那么该算法的失败率至多为 \(\frac{10}{11}\) 大约是 \(0.9\)。那么重复 \(T\) 次,至少有一次成功的概率大约是 \(1-0.9^T\)。
想要让失败概率 \(\le \delta\),只需要 \(0.9^T\le \delta\),\(T\) 取 \(O(\log \frac{1}{\delta})\) 即可。
对于一般的 \(\varepsilon\) 想让失败概率 \(\le \delta\),\(T\) 的取值是 \(O(\frac{1}{\varepsilon}\log\frac{1}{\delta})\)。
Median Trick
假设一个问题的回答是 Yes/No,我们的算法有 \(p>0.5\) 概率回答正确
考虑这样的算法,重复 \(T\) 次,取出现次数多的答案。
例如随机矩阵求一般图最大匹配那个算法,随机多个矩阵求 rank 的众数
我们有 Chernoff Bound:设有 \(n\) 个随机变量 \(X_1,X_2,\cdots,X_n\),且
设 \(X=\frac{1}{n}(X_1+X_2+\cdots+X_n)\)对于任意的 \(t\le \mu,\delta\in (0,1)\),有
即实战中我们希望取一个失败概率 \(\delta\),希望这 \(n\) 个变量的平均值距离它们的期望的差超过某个值 \(C\) 的概率不超过 \(\delta\)。那么 \(n\) 只要取的足够大使得 \(\sqrt{\frac{\log (1/\delta)}{nt}}\mu\le C\) 即可。这里 \(t\) 的作用是我们可能无法知道真正的期望值,不过只要知道其下界就可以做出一个足够有效的估计。
例如,假设抛一枚硬币正反面概率都是 \(0.5\),问抛多少次能够使得正面朝上出现的频率在 \([0.45,0.55]\) 中的概率至少是 \(1-\delta\)?
有 \(\mu=0.5\),那么只要 \(\sqrt{\frac{\log(1/\delta)\mu}{n}}\le 0.05\),\(n\) 取到 \(\frac{\log(1/\delta)\mu}{0.05^2}=O(\log (1/\delta))\) 即可。
生成随机采样
C++ 标准库有许多生成随机数的工具。
如果给定一个 \([0,1]\) 的均匀分布,如何得到其他的采样?例如需要得到一个离散变量 \(X\),其取到 \(X_1,\cdots,X_n\) 的概率分别为 \(p_1,\cdots,p_n\)。只需要把 \([0,1]\) 分成 \(n\) 段,第 \(i\) 段长度为 \(p_i\),然后随机取 \(r\in [0,1]\) 看分到哪一段内即可。
进一步给定连续变量 \(X\) 的分布 \(p(x)\) 之后也可以得到这个分布:取 \(r\in [0,1]\) 后取 \(x\) 使得 \(\int_{-\infty}^xp(x)\text{d}x=r\) 即可。
- Rejection Sampling
如果有一个离散随机变量 \(X\) 均匀随机取 \(0,1\),如何用 \(X\) 得到一个随机变量,使得 \(\frac{1}{3}\) 概率取 \(0\),\(\frac{2}{3}\) 概率取 \(1\)?
一种方法是把 \(X\) 重复两边,如果遇到 \(00\) 就重来,否则剩下 \(01,10,11\) 三种可能,一种取 \(0\) 两种取 \(1\) 即可。
期望重复的轮数是 \(\frac{4}{3}\) 次。这种通过拒绝某些输入的方法得到新的随机变量的方法叫 Rejection Sampling。
进一步,如果给定实数 \(p\in [0,1]\),如何生成一个以 \(p\) 概率取 \(1\) 的随机变量呢?
不难得到一个 \(O(\log M)\) 的算法,可以得到一个输出概率 \(q\) 满足 \(|q-p|\le \frac{1}{M}\) 的算法:取 \(M=2^k\),随机 \(k\) 个 \(\{0,1\}\) 得到一个 \([0,2^k)\) 中的数,比较它与 \(p\cdot 2^k\) 的大小关系即可。
更好的做法是,假设 \(p\) 的二进制表示为 \(p=\sum_{i=1}^{+\infty}2^{-i}p_i\),其中 \(p_i=\{0,1\}\),考虑以下的算法:
for i = 1 to infty
a = randint(0,1)
if a == p[i] : return a
else : continue
算法在第 \(i\) 次循环终止的概率为 \(2^{-i}\),在这一次生成 \(1\) 的概率为 \(p_i\),输出 \(1\) 的概率的确是 \(p=\sum 2^{-i}p_i\)。
然而运行的期望次数有比较大的改善,期望运行次数是 \(\sum i2^{-i}=O(1)\) 的。
Rejection Sampling 的另一个例子是亚线性空间回答“前缀采样”查询:给定一个序列 \(a\),预处理时允许访问任意的一个 \(a_i\),但只允许存储 \(O(\text{poly}(\log n))\) 量级的信息;接下来会进行一次询问,每次需要利用预处理的信息在一个前缀内 \([1,k]\) 随机取出某一个 \(a_i\)(\(1\le i\le k\))。需要保证均匀随机。
基本的想法是,我们考虑这样一个做法,每次从 \([1,n]\) 中随机取 \(i\),如果 \(i\le k\) 就返回 \(a_i\),否则接着取。不难发现这的确是在 \([1,i]\) 中均匀分布的;另一方面我们可以在预处理的时候进行这个随机过程,查询的时候直接查找序列中第一个 \(\le i\) 的元素。然而在 \(i\) 比较小的时候,预处理的序列长度需要达到期望 \(O(n)\) 级别。
一种解决办法是预处理 \(O(\log n)\) 个序列,第 \(i\) 个序列 \(S_i\) 在 \([1,2^i]\) 中均匀采样。查询 \([1,k]\) 时,找到最小的 \(j\) 满足 \(2^j\ge k\),然后在 \([1,2^j]\) 这个采样内找第一个 \(\le k\) 的即可。注意到此时必然有 \(2^{j-1}<k\le 2^j\),所以期望只需要两次;预处理的长度如果为 \(T\),全部失败的概率就是 \(0.5^T\)。
这个算法用确定 \(T\cdot \log n\) 的预处理信息达到了 \(0.5^T\) 的失败概率。
我们发现 \(S_i\) 中只有前缀最小值是有用的,考虑直接优化最开始的算法,我们在预处理的时候在那 \(O(n)\) 个数里面只保留前缀最小值,期望长度是 \(\ln n\) 的;为了避免 \(O(n)\) 次访问,我们直接从 \([1,p_i-1]\) 里面生成 \(p_{i+1}\),直到出现某一个 \(p_k=1\),这样期望 \(O(\log n)\) 次就会终止了。预处理信息量是期望 \(O(\log n)\),不过成功概率是 \(1\)。
如果要进行 \(q\) 次,必须每次运行之前都扔掉之前预处理的结果。不过嘛,预处理的复杂度也就是 \(O(\log n)\),还是很能接受的。
一个亚线性算法问题:求中位数
给定 \(n\) 个数,求它们的中位数。sort 的复杂度是 \(O(n\log n)\),当然也有 \(O(n)\) 的分治做法
我们有一个算法:随机采样 \(T\) 个 \(a_i\),直接取采样得到的中位数。这样做的期望当然是对的。
有这样的结论:\(T=O(1/\epsilon^2)\) 时,误差不超过 \(\pm 2\epsilon\),即大概率返回值的排名在 \([(0.5-2\epsilon)n,(0.5+2\epsilon)n]\) 中。
利用 Chernoff Bound 容易证明概率,我们考虑一下为啥 \(T\) 是 \(O(1/\epsilon^2)\):Chernoff Bound 里面需要有
意思就是,把一个数取到 \([1,(0.5-\epsilon)n]\) 内看成 \(1\),否则看成 \(0\),那么期望是 \(0.5-\epsilon\),我们希望不超过 \(0.5n\) 个数取到这里面,也就是限定 \(|X-\mu|\le \epsilon\)。那么就要满足上面那个式子,解出来就是 \(n=O(1/\epsilon^2)\)
类似地对 \([(0.5+\epsilon)n,n]\) 内也这样分析一遍,也可以得到类似的结论。草,好像我分析的很不严谨啊?

浙公网安备 33010602011771号