BZOJ4036:按位或 (min_max容斥&高维前缀和)
Description
刚开始你有一个数字0,每一秒钟你会随机选择一个[0,2^n-1]的数字,与你手上的数字进行或(c++,c的|,pascal
的or)操作。选择数字i的概率是p[i]。保证0<=p[i]<=1,Σp[i]=1问期望多少秒后,你手上的数字变成2^n-1。
Input
第一行输入n表示n个元素,第二行输入2^n个数,第i个数表示选到i-1的概率
Output
仅输出一个数表示答案,绝对误差或相对误差不超过1e-6即可算通过。如果无解则要输出INF
Sample Input
2
0.250.250.250.25
0.250.250.250.25
Sample Output
2.6666666667
HINT
对于100%的数据,n<=20
思路:可以min_max容斥来做,问题的关键就是求出得到所有子集X的期望F(X)就可以了,P(X)的概率为所有对X有贡献的p[x]之和(x是所有和X有交集的x,即便x含有X没有的部分);
我们倒过来求与X交集为空的部分的概率,即M^X的子集的概率,这部分可以用高维前缀和来做。
for(int i=0;i<N;i++) for(int j=0;j<(1<<N);j++) if(j&(1<<i)) F[j]+=F[j^(1<<i)]; 这个代码可以得到子集的前缀和。 复杂度是O(N*2^N)
超集的写法:
for(int i=0;i<N;i++) for(int j=0;j<(1<<N);j++) if(!j&(1<<i)) F[j]+=F[j|(1<<i)];
#include<bits/stdc++.h> using namespace std; const int maxn=1<<21; double P[maxn],ans;int N,sum,M; void dfs(int pos,int now,int cnt) { if(pos==N){ if(cnt>=1){ if(cnt&1) ans+=1.0/(1.0-P[(M-1)^now]); else ans-=1.0/(1.0-P[(M-1)^now]); } return ; } dfs(pos+1,now|(1<<pos),cnt+1); dfs(pos+1,now,cnt); } int main() { scanf("%d",&N); M=1<<N; for(int i=0;i<M;i++){ scanf("%lf",&P[i]); if(P[i]>0) sum|=i; } if(sum!=M-1) return puts("INF"),0; for(int i=0;i<N;i++) for(int j=0;j<M;j++) if(j&(1<<i)) P[j]+=P[j^(1<<i)]; dfs(0,0,0); printf("%.6lf\n",ans); return 0; } //2 0.25 0.25 0.25 0.25
当然,为1的个数我们也可以预处理出来。就不用DFS了。
#include<bits/stdc++.h> using namespace std; const int maxn=1<<21; double P[maxn],ans;int N,sum,M,num[maxn]; int main() { scanf("%d",&N); M=1<<N; for(int i=0;i<M;i++){ scanf("%lf",&P[i]); if(P[i]>0) sum|=i; } if(sum!=M-1) return puts("INF"),0; for(int i=1;i<M;i++) num[i]=num[i>>1]+(i&1); for(int i=0;i<N;i++) for(int j=0;j<M;j++) if(j&(1<<i)) P[j]+=P[j^(1<<i)]; for(int i=1;i<M;i++) ans+=(num[i]&1?1.0:-1.0)/(1-P[(M-1)^i]); printf("%.6lf\n",ans); return 0; } //2 0.25 0.25 0.25 0.25
It is your time to fight!