zoukankan      html  css  js  c++  java
  • Min-Max容斥及其推广和应用

    概念

    Min-Max容斥,又称最值反演,是一种对于特定集合,在已知最小值或最大值中的一者情况下,求另一者的算法。

    例如:

    [max(a,b)=a+b-min(a,b) \ max(a,b,c)=a+b+c-min(a,b)-min(a,c)-min(b,c)+min(a,b,c) ]

    显然,将所有数取相反数,易知用最大值求最小值的公式与用最小值求最大值的公式形式相同。以下只讨论用最小值求最大值的方法。

    形式

    (Max(S)) 表示集合 (S) 的最大值,(Min(S)) 表示集合 (S) 的最小值,则:

    [Max(S)=sumlimits_{Tsubset S,T eq phi}(-1)^{|T|-1}Min(T) ]

    推导

    设存在一个以集合大小为自变量的函数 (f) 满足 (Max(S)=sumlimits_{Tsubset S,T eq phi}f(|T|)Min(T))

    (S) 中元素从大到小排列为 (x_1,x_2,...,x_m) ,则对于 (x_i) ,其在左侧的贡献为 ([i=1]) ,在右侧的贡献为 (sumlimits_{j=0}^{i-1}{i-1choose j}f(j+1))

    若等式成立,则必有 ([i=1]=sumlimits_{j=0}^{i-1}{i-1choose j}f(j+1))

    (F(i)=[i+1=1]) (即 ([i=1]=F(i-1)) ),(G(i)=f(i+1)) ,则 (F(i)=sumlimits_{j=0}^{i}{ichoose j}G(j))

    进行二项式反演,得 (G(i)=sumlimits_{j=0}^{i}(-1)^{i-j}{ichoose j}F(j)=(-1)^i)

    (f(i)=G(i-1)=(-1)^{i-1})

    因此构造成立,故:

    [Max(S)=sumlimits_{Tsubset S,T eq phi}(-1)^{|T|-1}Min(T) ]

    另一种证明

    考虑等式右侧,记最小值为 (x_i) ,则:

    • (i=1) 时,贡献为 (1)
    • (i eq 1) 时,则 (x_1) 有选和不选两种方案,这两种方案一一对应且系数恰为相反数,故总贡献为 (0)

    故右侧的总和就是 (x_1=Max(S))

    推广

    (kMax(S)) 表示集合 (S) 的第 (k) 大值,则:

    [kMax(S)=sumlimits_{Tsubset S,|T|ge k}(-1)^{|T|-k}{|T|-1choose k-1}Min(T) ]

    推导

    设存在一个以集合大小为自变量的函数 (g) 满足 (kMax(S)=sumlimits_{Tsubset S,T eq phi}g(|T|)Min(T))

    (S) 中元素从大到小排列为 (x_1,x_2,...,x_m) ,则对于 (x_i) ,其在左侧的贡献为 ([i=k]) ,在右侧的贡献为 (sumlimits_{j=0}^{i-1}{i-1choose j}g(j+1))

    若等式成立,则必有 ([i=k]=sumlimits_{j=0}^{i-1}{i-1choose j}g(j+1))

    (F(i)=[i+1=k]) (即 ([i=k]=F(i-1)) ),(G(i)=g(i+1)) ,则 (F(i)=sumlimits_{j=0}^{i}{ichoose j}G(j))

    进行二项式反演,得 (G(i)=sumlimits_{j=0}^{i}(-1)^{i-j}{ichoose j}F(j)=(-1)^{i-k+1}{ichoose k-1})

    (f(i)=G(i-1)=(-1)^{i-k}{i-1choose k-1})

    因此构造成立,故:

    [kMax(S)=sumlimits_{Tsubset S,|T|ge k}(-1)^{|T|-k}{|T|-1choose k-1}Min(T) ]

    应用

    Min-Max容斥及其推广常用于解决“都出现的期望时间”问题,处理方法:

    (t_i) 表示第 (i) 个元素的出现时间,则:

    • (Max(S)) 表示 (S)(t) 的最大值,即所有元素出现时间的最大值,即所有元素都出现的时间;
    • (Min(S)) 表示 (S)(t) 的最小值,即所有元素出现时间的最小值,即至少有一个出现的时间。

    根据Min-Max容斥,有 (Max(S)=sumlimits_{Tsubset S,T eq phi}(-1)^{|T|-1}Min(T))

    对左右同时取期望,由于线性,期望可以直接放到求和符号里面,即 (E(Max(S))=sumlimits_{Tsubset S,T eq phi}(-1)^{|T|-1}E(Min(T)))

    容易发现 (E(Min(T))) 求起来十分容易:当单位时间出现 (T) 中至少一个的概率为 (p) ,则出现 (T) 中至少一个的期望时间为 (frac 1p)

    于是通过公式即可求出 (Max(S)) ,即所有元素都出现的期望时间。

    对于Min-kMax容斥同理。

    写法

    如果只需要求出 (Max(U)) ,即全集的最大值的话,只需要计算每个自己对全集的贡献即可。

    如果要对所有 (S)(Max(S)) 的话(尽管似乎还没遇到过),一种较快的方法是用按位分治来代替枚举子集。有两种常用写法,它们稍加处理就可以变成公式中的形式。

    写法一

    for(i = 1 ; i < (1 << n) ; i <<= 1)
        for(j = 0 ; j < (1 << n) ; j ++ )
            if(j & i)
                f[j] -= f[i];
    

    此时系数是 ((-1)^{|S|-|T}) .

    写法二

    for(i = 1 ; i < (1 << n) ; i <<= 1)
        for(j = 0 ; j < (1 << n) ; j ++ )
            if(j & i)
                f[j] = f[i] - f[j];
    

    此时系数是 ((-1)^{|T|})

    例题

    [hdu4336]Card Collector

    题目大意

    (n) 种卡片,每次购买有 (p_i) 的概率买到第 (i) 种,求使得每种都买到的期望购买次数。

    (1le nle 20)

    题解

    Min-Max容斥基础题,参见上面的 “应用” 部分。

    对于本题,有 (Min(S)=frac 1{sumlimits_{iin S}p_i}) ,然后套用 (Min-Max) 容斥的公式即可。

    时间复杂度 (O(2^n))

    #include <cstdio>
    #define N 1100010
    int cnt[N];
    double p[N] , f[N];
    int main()
    {
        int n , i , j;
        double ans;
        while(~scanf("%d" , &n))
        {
            ans = 0;
            for(i = 0 ; i < n ; i ++ ) scanf("%lf" , &p[1 << i]);
            for(i = 1 ; i < (1 << n) ; i ++ ) f[i] = f[i - (i & (-i))] + p[i & (-i)] , cnt[i] = cnt[i - (i & -i)] + 1;
            for(i = 1 ; i < (1 << n) ; i ++ ) ans += ((cnt[i] & 1) ? 1 : -1) / f[i];
            printf("%lf
    " , ans);
        }
        return 0;
    }
    

    [bzoj4036]按位或

    题目大意

    你初始有数字 (0) ,每次操作会随机选择 ([0,2^n-1]) 的一个数字与你的数字进行按位或运算,选到数 (i) 的概率为 (p_i) 。求使得你的数字变为 (2^n-1) 的期望操作次数。

    (1le nle 20)

    题解

    和上一题类似,问题转化为计算 (Min(S)) ,即需要求出所有与 (S) 有公共元素(取与不为 (0) )的 (p) 之和。

    正难则反,考虑求所有与 (S) 无公共元素的 (p) 之和,即 (S) 的补集 (2^n-1-S) 的所有子集的 (p) 之和,使用按位分治来解决。

    最后套公式计算即可。无解的判定通过判断是否某一位都存在一个 (p eq 0) 的元素来处理。

    时间复杂度 (O(n imes 2^n))

    代码

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    double p[1100010];
    int cnt[1100010];
    int main()
    {
        int n , i , j;
        double ans = 0;
        scanf("%d" , &n);
        for(i = 0 ; i < (1 << n) ; i ++ ) scanf("%lf" , &p[i]);
        for(i = 1 ; i < (1 << n) ; i ++ ) cnt[i] = cnt[i - (i & -i)] + 1;
        for(i = 1 ; i < (1 << n) ; i <<= 1)
        {
            for(j = 0 ; j < (1 << n) ; j ++ )
                if((j & i) && p[j])
                    break;
            if(j == (1 << n))
            {
                puts("INF");
                return 0;
            }
        }
        for(i = 1 ; i < (1 << n) ; i <<= 1)
            for(j = 0 ; j < (1 << n) ; j ++ )
                if(j & i)
                    p[j] += p[j ^ i];
        for(i = 1 ; i < (1 << n) ; i ++ ) ans += ((cnt[i] & 1) ? 1 : -1) / (1 - p[(1 << n) - 1 - i]);
        printf("%.8lf
    " , ans);
        return 0;
    }
    

    [luogu4707]重返现世

    题目大意

    (n) 种物质,每单位时间会随机生成一种物质,生成第 (i) 种物质的概率为 (frac{p_i}m) 。求获得 (k) 种物质的期望时间。

    (1le nle 1000)(1le mle 10000)(1le kle n)(p_i) 为整数且 (sumlimits_{i=1}^np_i=m)(n-kle 10)

    题解

    获得 (k) 种物质,相当于求所有获得时间中第 (n-k+1) 大的,问题转化为Min-kMax容斥问题。方便起见,以下令 (q=n-k+1) ,则有 (1le qle 11)

    由于 (n)(1000) 之大,使用前两道题的子集统计方法显然会直接暴毙。

    思考:尽管我们的集合选取方案有 (2^n) 种,但每种的 (Min(S)) 只和 (sumlimits_{iin S}p_i) 有关,因此状态数其实只有 (m) 种。

    一个比较显然的思路是设 (f_{i,j,l}) 表示前 (i) 种物质选出 (j) 种,凑齐 (sum p=l) 的方案数,然而数据范围过大,无法通过此题。

    到此为止,我们还有一个条件没有用到:(qle 11)

    考虑在已知 (f_{i,j,l}) 后答案的计算,贡献为 ((-1)^{j-q}{j-1choose q-1} imes frac ml imes f_{i,j,l}) 。对于前面的部分,运用组合数公式,有:

    [(-1)^{j-q}{j-1choose q-1}=(-1)^{(j-1)-(q-1)}{j-2choose q-2}-(-1)^{(j-1)-q}{j-2choose q-1} ]

    (f_{i,j,l}) 又有转移 (f_{i,j,l}=f_{i-1,j,l}+f_{i-1,j-1,l-p_i}) ,故:

    [(-1)^{j-q}{j-1choose q-1}f_{i,j,l}=(-1)^{j-q}{j-1choose q-1}f_{i-1,j,l}+(-1)^{(j-1)-(q-1)}{j-2choose q-2}f_{i-1,j-1,l-p_i}-(-1)^{(j-1)-q}{j-2choose q-1}f_{i-1,j-1,l-p_i} ]

    发现了什么?前面的系数只和 (j) 与计算答案时所用的 (q) 有关,因此设 (g_{i,j,l,t}) 表示前 (i) 种物质选出 (j) 种,凑齐 (sum p=l) ,且最终计算时的 (q=t) 的系数乘以方案数。则有:

    [g_{i,j,l,t}=g_{i-1,j,l,t}+g_{i-1,j-1,l-p_i,t-1}-g_{i-1,j-1,l-p_i,t} ]

    我们所做的似乎都是无用功。但事实上,仔细观察就会发现 (j) 的一维已经没有用处,无论是转移还是最终答案都不需要用到 (j)

    左右对 (j) 那一维求和,便有 (h_{i,l,t}) 表示前 (i) 种物质选出若干种,凑齐 (sum p=l) ,且最终计算时的 (q=t) 的系数乘以方案数,则有:

    [h_{i,l,t}=h_{i-1,l,t}+h_{i-1,l-p_i,t-1}-h_{i-1,l-p_i,t} ]

    最终答案就是 (sumlimits_{i=1}^mh_{n,i,q} imes frac mi)

    时间复杂度 (O(nmq)) ,由于空间不足,需要使用滚动数组。

    代码

    #include <cstdio>
    #include <cstring>
    #define mod 998244353
    typedef long long ll;
    ll p[1010] , f[2][10010][11];
    inline ll qpow(ll x , ll y)
    {
        ll ans = 1;
        while(y)
        {
            if(y & 1) ans = ans * x % mod;
            x = x * x % mod , y >>= 1;
        }
        return ans;
    }
    int main()
    {
        int n , t , m , i , j , k , d;
        ll ans = 0;
        scanf("%d%d%d" , &n , &t , &m) , t = n - t + 1;
        for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &p[i]);
        for(i = 1 ; i <= t ; i ++ ) f[0][0][i] = -1;
        for(d = i = 1 ; i <= n ; i ++ , d ^= 1)
        {
            memcpy(f[d] , f[d ^ 1] , sizeof(f[d]));
            for(j = p[i] ; j <= m ; j ++ )
                for(k = 1 ; k <= t ; k ++ )
                    f[d][j][k] = (f[d][j][k] + f[d ^ 1][j - p[i]][k - 1] - f[d ^ 1][j - p[i]][k] + mod) % mod;
        }
        for(i = 1 ; i <= m ; i ++ ) ans = (ans + f[n & 1][i][t] * m % mod * qpow(i , mod - 2)) % mod;
        printf("%lld
    " , ans);
        return 0;
    }
    
  • 相关阅读:
    python中的os
    文件系统的简单操作
    文件与目录管理
    用户与用户组管理
    基础命令的操作
    linux开机流程
    ansible源码安装、普通用户实现批量控制
    python3中得数据类型
    判断一个字符串中得大写字母,小写字母,数字出现得次数
    Elasticsearch 如何安全加固
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/11563330.html
Copyright © 2011-2022 走看看