[luogu 3175] [HAOI2015]按位或
题面
刚开始你有一个数字0,每一秒钟你会随机选择一个[0,2^n-1]的数字,与你手上的数字进行按位或运算。问期望多少秒后,你手上的数字变成2^n-1。
分析
前置知识:min-max容斥
记(max(S))为集合(S)中的最大值,(min(S))为集合(S)中的最小值(如果(S=emptyset)
,那(max(S)=min(S)=0)),那么有
[max(S)=sum _{Tsubseteq S}left( -1 ight) ^{|T|-1} min(T) ]
这里感性理解一下就好了
前置知识:高维前缀和
把二进制数看成一个集合,第i位为1表示集合里有元素i.设全集(U)为二进制数(2^n-1)对应的集合
设(max(S))为S中最后一个元素被或为1的期望时间,min就是最先被或为1的元素的期望时间,那么答案就是(max(U))
根据min-max容斥,我们有
[max(S)=sum_{T subseteq S} (-1)^{|T|-1} min(T)
]
因为是最先被或为1,根据定义我们有
[min(T)=frac{1}{sum_{X subseteq U , X cap T
eq emptyset }p(x)}
]
那么
[egin{aligned} min(T) &= frac{1}{sum_{X subseteq U , X cap T
eq emptyset }p(x)} \ &=frac{1}{1-sum_{X subseteq U,X cap T = emptyset} p(x)} \&= frac{1}{1-sum_{X subseteq U-T} p(x)}end{aligned}
]
其实就是用了2次补集转化,然后(sum_{X subseteq U-T} p(x))显然就是一个高维前缀和,直接套模板就可以了
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 20
#define eps 1e-10
using namespace std;
int n;
double p[(1<<maxn)+5];
int count_1(int x){
int ans=0;
while(x){
if(x&1) ans++;
x>>=1;
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=0;i<(1<<n);i++) scanf("%lf",&p[i]);
for(int i=0;i<n;i++){
for(int j=0;j<(1<<n);j++){
if(j&(1<<i)) p[j]+=p[j^(1<<i)];
}
}
double ans=0;
int all=(1<<n)-1;
for(int i=1;i<=all;i++){
if(fabs(1-p[all^i])<eps) continue;//防止除0错误
ans+=pow(-1,count_1(i)-1)*1/(1-p[all^i]);
}
if(fabs(ans)<eps) printf("INF");
else printf("%.10lf",ans);
}