Min-Max容斥,又称最值反演,是一种对于特定集合,在已知最小值或最大值中的一者状况下,求另外一者的算法。php
例如:算法
$$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)=\sum\limits_{T\subset S,T\neq \phi}(-1)^{|T|-1}Min(T)
$$spa
设存在一个以集合大小为自变量的函数 $f$ 知足 $Max(S)=\sum\limits_{T\subset S,T\neq \phi}f(|T|)Min(T)$ 。code
记 $S$ 中元素从大到小排列为 $x_1,x_2,...,x_m$ ,则对于 $x_i$ ,其在左侧的贡献为 $[i=1]$ ,在右侧的贡献为 $\sum\limits_{j=0}^{i-1}{i-1\choose j}f(j+1)$ 。get
若等式成立,则必有 $[i=1]=\sum\limits_{j=0}^{i-1}{i-1\choose j}f(j+1)$ 。string
设 $F(i)=[i+1=1]$ (即 $[i=1]=F(i-1)$ ),$G(i)=f(i+1)$ ,则 $F(i)=\sum\limits_{j=0}^{i}{i\choose j}G(j)$ 。it
进行二项式反演,得 $G(i)=\sum\limits_{j=0}^{i}(-1)^{i-j}{i\choose j}F(j)=(-1)^i$ 。io
故 $f(i)=G(i-1)=(-1)^{i-1}$ 。
所以构形成立,故:
$$
Max(S)=\sum\limits_{T\subset S,T\neq \phi}(-1)^{|T|-1}Min(T)
$$
考虑等式右侧,记最小值为 $x_i$ ,则:
故右侧的总和就是 $x_1=Max(S)$ 。
记 $kMax(S)$ 表示集合 $S$ 的第 $k$ 大值,则:
$$
kMax(S)=\sum\limits_{T\subset S,|T|\ge k}(-1)^{|T|-k}{|T|-1\choose k-1}Min(T)
$$
设存在一个以集合大小为自变量的函数 $g$ 知足 $kMax(S)=\sum\limits_{T\subset S,T\neq \phi}g(|T|)Min(T)$ 。
记 $S$ 中元素从大到小排列为 $x_1,x_2,...,x_m$ ,则对于 $x_i$ ,其在左侧的贡献为 $[i=k]$ ,在右侧的贡献为 $\sum\limits_{j=0}^{i-1}{i-1\choose j}g(j+1)$ 。
若等式成立,则必有 $[i=k]=\sum\limits_{j=0}^{i-1}{i-1\choose j}g(j+1)$ 。
设 $F(i)=[i+1=k]$ (即 $[i=k]=F(i-1)$ ),$G(i)=g(i+1)$ ,则 $F(i)=\sum\limits_{j=0}^{i}{i\choose j}G(j)$ 。
进行二项式反演,得 $G(i)=\sum\limits_{j=0}^{i}(-1)^{i-j}{i\choose j}F(j)=(-1)^{i-k+1}{i\choose k-1}$ 。
故 $f(i)=G(i-1)=(-1)^{i-k}{i-1\choose k-1}$ 。
所以构形成立,故:
$$
kMax(S)=\sum\limits_{T\subset S,|T|\ge k}(-1)^{|T|-k}{|T|-1\choose k-1}Min(T)
$$
Min-Max容斥及其推广经常使用于解决“都出现的指望时间”问题,处理方法:
记 $t_i$ 表示第 $i$ 个元素的出现时间,则:
根据Min-Max容斥,有 $Max(S)=\sum\limits_{T\subset S,T\neq \phi}(-1)^{|T|-1}Min(T)$ 。
对左右同时取指望,因为线性,指望能够直接放到求和符号里面,即 $E(Max(S))=\sum\limits_{T\subset S,T\neq \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|}$ 。
有 $n$ 种卡片,每次购买有 $p_i$ 的几率买到第 $i$ 种,求使得每种都买到的指望购买次数。
$1\le n\le 20$ 。
Min-Max容斥基础题,参见上面的 “应用” 部分。
对于本题,有 $Min(S)=\frac 1{\sum\limits_{i\in 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\n" , ans); } return 0; }
你初始有数字 $0$ ,每次操做会随机选择 $[0,2^n-1]$ 的一个数字与你的数字进行按位或运算,选到数 $i$ 的几率为 $p_i$ 。求使得你的数字变为 $2^n-1$ 的指望操做次数。
$1\le n\le 20$ 。
和上一题相似,问题转化为计算 $Min(S)$ ,即须要求出全部与 $S$ 有公共元素(取与不为 $0$ )的 $p$ 之和。
正难则反,考虑求全部与 $S$ 无公共元素的 $p$ 之和,即 $S$ 的补集 $2^n-1-S$ 的全部子集的 $p$ 之和,使用按位分治来解决。
最后套公式计算便可。无解的断定经过判断是否某一位都存在一个 $p\neq 0$ 的元素来处理。
时间复杂度 $O(n\times 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\n" , ans); return 0; }
有 $n$ 种物质,每单位时间会随机生成一种物质,生成第 $i$ 种物质的几率为 $\frac{p_i}m$ 。求得到 $k$ 种物质的指望时间。
$1\le n\le 1000$ ,$1\le m\le 10000$ ,$1\le k\le n$ ,$p_i$ 为整数且 $\sum\limits_{i=1}^np_i=m$ ,$n-k\le 10$ 。
得到 $k$ 种物质,至关于求全部得到时间中第 $n-k+1$ 大的,问题转化为Min-kMax容斥问题。方便起见,如下令 $q=n-k+1$ ,则有 $1\le q\le 11$ 。
因为 $n$ 有 $1000$ 之大,使用前两道题的子集统计方法显然会直接暴毙。
思考:尽管咱们的集合选取方案有 $2^n$ 种,但每种的 $Min(S)$ 只和 $\sum\limits_{i\in S}p_i$ 有关,所以状态数其实只有 $m$ 种。
一个比较显然的思路是设 $f_{i,j,l}$ 表示前 $i$ 种物质选出 $j$ 种,凑齐 $\sum p=l$ 的方案数,然而数据范围过大,没法经过此题。
到此为止,咱们还有一个条件没有用到:$q\le 11$ 。
考虑在已知 $f_{i,j,l}$ 后答案的计算,贡献为 $(-1)^{j-q}{j-1\choose q-1}\times \frac ml\times f_{i,j,l}$ 。对于前面的部分,运用组合数公式,有:
$$
(-1)^{j-q}{j-1\choose q-1}=(-1)^{(j-1)-(q-1)}{j-2\choose q-2}-(-1)^{(j-1)-q}{j-2\choose 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-1\choose q-1}f_{i,j,l}=(-1)^{j-q}{j-1\choose q-1}f_{i-1,j,l}+(-1)^{(j-1)-(q-1)}{j-2\choose q-2}f_{i-1,j-1,l-p_i}-(-1)^{(j-1)-q}{j-2\choose 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}
$$
最终答案就是 $\sum\limits_{i=1}^mh_{n,i,q}\times \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\n" , ans); return 0; }