概念
Min-Max容斥,又稱最值反演,是一種對於特定集合,在已知最小值或最大值中的一者情況下,求另一者的算法。
例如:
顯然,將所有數取相反數,易知用最大值求最小值的公式與用最小值求最大值的公式形式相同。以下只討論用最小值求最大值的方法。
形式
記 \(Max(S)\) 表示集合 \(S\) 的最大值,\(Min(S)\) 表示集合 \(S\) 的最小值,則:
推導
設存在一個以集合大小為自變量的函數 \(f\) 滿足 \(Max(S)=\sum\limits_{T\subset S,T\neq \phi}f(|T|)Min(T)\) 。
記 \(S\) 中元素從大到小排列為 \(x_1,x_2,...,x_m\) ,則對於 \(x_i\) ,其在左側的貢獻為 \([i=1]\) ,在右側的貢獻為 \(\sum\limits_{j=0}^{i-1}{i-1\choose j}f(j+1)\) 。
若等式成立,則必有 \([i=1]=\sum\limits_{j=0}^{i-1}{i-1\choose j}f(j+1)\) 。
設 \(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)\) 。
進行二項式反演,得 \(G(i)=\sum\limits_{j=0}^{i}(-1)^{i-j}{i\choose j}F(j)=(-1)^i\) 。
故 \(f(i)=G(i-1)=(-1)^{i-1}\) 。
因此構造成立,故:
另一種證明
考慮等式右側,記最小值為 \(x_i\) ,則:
- 當 \(i=1\) 時,貢獻為 \(1\) ;
- 當 \(i\neq 1\) 時,則 \(x_1\) 有選和不選兩種方案,這兩種方案一一對應且系數恰為相反數,故總貢獻為 \(0\) 。
故右側的總和就是 \(x_1=Max(S)\) 。
推廣
記 \(kMax(S)\) 表示集合 \(S\) 的第 \(k\) 大值,則:
推導
設存在一個以集合大小為自變量的函數 \(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}\) 。
因此構造成立,故:
應用
Min-Max容斥及其推廣常用於解決“都出現的期望時間”問題,處理方法:
記 \(t_i\) 表示第 \(i\) 個元素的出現時間,則:
- \(Max(S)\) 表示 \(S\) 中 \(t\) 的最大值,即所有元素出現時間的最大值,即所有元素都出現的時間;
- \(Min(S)\) 表示 \(S\) 中 \(t\) 的最小值,即所有元素出現時間的最小值,即至少有一個出現的時間。
根據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|}\) 。
例題
[hdu4336]Card Collector
題目大意
有 \(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;
}
[bzoj4036]按位或
題目大意
你初始有數字 \(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;
}
[luogu4707]重返現世
題目大意
有 \(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}\) 。對於前面的部分,運用組合數公式,有:
而 \(f_{i,j,l}\) 又有轉移 \(f_{i,j,l}=f_{i-1,j,l}+f_{i-1,j-1,l-p_i}\) ,故:
發現了什么?前面的系數只和 \(j\) 與計算答案時所用的 \(q\) 有關,因此設 \(g_{i,j,l,t}\) 表示前 \(i\) 種物質選出 \(j\) 種,湊齊 \(\sum p=l\) ,且最終計算時的 \(q=t\) 的系數乘以方案數。則有:
我們所做的似乎都是無用功。但事實上,仔細觀察就會發現 \(j\) 的一維已經沒有用處,無論是轉移還是最終答案都不需要用到 \(j\)。
左右對 \(j\) 那一維求和,便有 \(h_{i,l,t}\) 表示前 \(i\) 種物質選出若干種,湊齊 \(\sum p=l\) ,且最終計算時的 \(q=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;
}