MinMax容斥学习笔记(HAOI2015按位或简要题解)
先给出公式吧
$Max(S) = \sum_{T \subseteq S} (-1)^{|T| - 1} Min(T)$
特别地对于两边同时取期望.并因为期望的线性性,有:
$E(Max(S)) = \sum_{T \subseteq S} (-1)^{|T| - 1} E(Min(T))$
### proof
考虑设$Max(S) = \sum_{T \subseteq S} g(|T|) * Min(T)$
令S的元素从小到大排序为$x_1,x_2,....x_{|S|}$对于某个$x_i$,其对左边的贡献为$[i == 1]$,对于右边的贡献为$\sum \binom{i-1}{j} * g(j + 1)$,即$[i == 1] = \sum \binom{i-1}{j} * g(j)$,二项式反演即可得到$g$
考虑期望形式,发现$E(Max(S))$表示该集合所有元素都出现的期望时间,这个往往不好算.但是$E(Min(T))$表示至少有一个出现的期望时间,这个往往好算,这个时候就可以用$MinMax$容斥.
例题:[HAOI2015按位或]
题意简述:
你手中有一个数,初始为0
每次以概率$p_i$给你一个数字$i,(i < 2^n)$,你把手中的数或上它,求使得你手上的数第一次变成$2^n - 1$的期望时间
$n < 20$
考虑设集合$S$中的位全部拥有的期望时间为$f(S)$,容易发现$f(S) = E(Max(S))$,答案即为$f(2^n - 1)$,考虑$E(Min(T))$怎么算.发现
$E(Min(S)) = \sum_{[S0 \& S != 0]} p(S0)$,发现对于S取反便等价于求子集和.
### tips
1. $fwt(or)$等价于求子集和
2. $fwt(and)$等价于超集和
所以做一遍$fwt$就做完了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/*minmax容斥*/ #include<bits/stdc++.h> using namespace std; #define ll long long int read(){ char c = getchar(); int x = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar(); return x; } const int N = (1 << 20) | 1; double dp[N],p[N]; double pr[N]; double E; int pop[N]; void fwt(double *f,int n,int type){ for(int j = 1; j < n; j <<= 1){ for(int i = 0,len = j << 1; i < n; i += len){ for(int k = 0; k < j; ++k){ double x = f[i+k],y = f[i+k+j]; if(type == 1) f[i+k+j] = x + y; else f[i+k+j] = -x + y; } } } } #define lowbit(x) (x & (-x)) int main(){ int k = read(); for(int i = 0; i < (1 << k); ++i) scanf("%lf",&p[i]); for(int S = 1; S < (1 << k); ++S){ pop[S] = pop[S-lowbit(S)] + 1; /*int nS = ((~S) & ((1 << k) - 1)); for(int S0 = nS; S0; S0 = (S0 - 1) & nS){ dp[S] += p[S0]; }dp[S] += p[0];dp[S] = 1 - dp[S];*/ } // printf("%.10lf\n",dp[1]); for(int S = 0; S < (1 << k); ++S) pr[S] = p[S]; int n = (1 << k); fwt(pr,n,1); for(int S = 1; S < (1 << k); ++S){ int nS = ((~S) & ((1 << k) - 1)); dp[S] = 1 - pr[nS]; } // printf("%.10lf\n",dp[1]); for(int i = 0; i < k; ++i){ if(dp[1<<i] == 0){ puts("INF"); return 0; } } for(int S = 1; S < (1 << k); ++S){ int cnt = pop[S] - 1; // printf("%d %.10lf\n",S,dp[S]); double v = 1.0 / dp[S]; if(cnt & 1) E -= v; else E += v; } printf("%.10lf\n",E); return 0; }