洛谷 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;
}
posted @ 2021-01-18 20:47  eee_hoho  阅读(63)  评论(0编辑  收藏  举报