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$就做完了
 
/*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;
}
View Code

 

posted @ 2021-03-04 22:29  y_dove  阅读(121)  评论(0编辑  收藏  举报