洛谷 P3175 [HAOI2015]按位或
刚开始你有一个数字 \(0\),每一秒钟你会随机选择一个 \([0,2^n-1]\) 的数字,与你手上的数字进行或(C++,C 的 |
,pascal 的 or
)操作。选择数字 \(i\) 的概率是 \(p_i\)。保证 \(0\leq p_i \leq 1\),\(\sum p_i=1\) 。问期望多少秒后,你手上的数字变成 \(2^n-1\)。
\(n\leq 20\)。
我们发现可以按位考虑,设 \(t_i\) 表示第 \(i\) 为被取到的期望时间,那么 \(ans=\max_i t_i\) 。
于是我们可以设 \(E(max(S))\) 和 \(E(min(S))\) 分别表示 \(S\) 中每一位都取到的期望时间和有一位被取到的时间,那么 \(ans=E(max(U))\) 。
考虑 min-max 容斥,就有:
\[E(max(U))=\sum_{S\subseteq U}(-1)^{|S|+1}E(min(S))
\]
考虑如何求 \(E(min(S))\) ,先枚举操作次数 \(k\) ,那么一定是前 \(k-1\) 次选到了 \(S\) 补集的子集,最后一次选到了 \(S\) 的子集,设 \(P(S)\) 表示选到 \(S\) 的子集的概率,那么就有:
\[E(min(S))=\sum_{i=1}^{\infty}iP^{k-1}(U-S)(1-P(U-S))=(1-P(U-S))\sum_{i=1}^{\infty}iP^{k-1}(U-S)
\]
后面的可以错位相减然后等比数列求和,最后得到:
\[E(min(S))=\frac{1}{1-P(U-S)}
\]
然后需要求 \(P(S)=\sum_{T\subseteq S}p_T\) ,相当于求子集和,而异或 FWT 刚好可以求子集和,因为这个矩阵:
\[\begin{bmatrix}1&0\\1&1\end{bmatrix}
\]
刚好就是 \(c(i,j)=[i\&j==j]\) ,就相当于子集求和了。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 20;
using namespace std;
int n;
double p[1 << N],ans;
void FWT(double *a)
{
for (int i = 1;i < (1 << n);i <<= 1)
for (int j = 0;j < (1 << n);j += i << 1)
for (int k = 0;k < i;k++)
{
double a0 = a[j + k],a1 = a[j + k + i];
a[j + k] = a0;
a[j + k + i] = a0 + a1;
}
}
int main()
{
scanf("%d",&n);
for (int i = 0;i < (1 << n);i++)
scanf("%lf",&p[i]);
FWT(p);
for (int s = 1;s < (1 << n);s++)
{
int cnt = 0;
for (int i = 0;i < n;i++)
if ((s >> i) & 1)
cnt++;
if (1 - p[((1 << n) - 1) ^ s] == 0)
{
cout<<"INF"<<endl;
return 0;
}
ans += 1.0 / (1 - p[((1 << n) - 1) ^ s]) * (cnt % 2 == 0 ? -1 : 1);
}
printf("%.9lf\n",ans);
return 0;
}