min-max容斥学习笔记
最近报了航电的春季赛,在一道题目里面遇到了做法,感觉挺有意思。
考虑一个(多重)集合S={ai}S=\{a_i\}S={ai},有如下的等式成立
minai∈S(ai)=∑T⊆S,T≠∅(−1)∣T∣−1maxai∈T(ai)\min_{a_i\in S}(a_i)=\sum_{T\subseteq S,T\ne \empty}(-1)^{|T|-1}\max_{a_i\in T}(a_i)ai∈Smin(ai)=T⊆S,T=∅∑(−1)∣T∣−1ai∈Tmax(ai)
反之亦然,
maxai∈S(ai)=∑T⊆S,T≠∅(−1)∣T∣−1minai∈T(ai)\max_{a_i\in S}(a_i)=\sum_{T\subseteq S,T\ne \empty}(-1)^{|T|-1}\min_{a_i\in T}(a_i)ai∈Smax(ai)=T⊆S,T=∅∑(−1)∣T∣−1ai∈Tmin(ai)
这两个公式还是比较容易理解的,这里只拿第一条式子进行分析。
首先是最小的元素a1a_1a1,只有T={a1}T=\{a_1\}T={a1}的时候才有a1a_1a1的贡献。
对于其它的元素,假设有kkk个比它小的数,那么这个元素总的贡献就是∑i=0k(−1)iCki=(1−1)k=0\sum_{i=0}^k(-1)^iC_k^i=(1-1)^k=0∑i=0k(−1)iCki=(1−1)k=0,因此这么容斥下来就会得到最小的数。
回到这一题中,因为有期望,所以这两个式子不能直接用。但因为期望实际上把全部情况加起来,因此可以直接把两边加上期望,即
E(minai∈S(ai))=E(∑T⊆S,T≠∅(−1)∣T∣−1maxai∈T(ai))E(\min_{a_i\in S}(a_i))=E(\sum_{T\subseteq S,T\ne \empty}(-1)^{|T|-1}\max_{a_i\in T}(a_i))E(ai∈Smin(ai))=E(T⊆S,T=∅∑(−1)∣T∣−1ai∈Tmax(ai))
在这一题中,集合就是没有被污染的行和列,而max操作就意味着要把集合行和列全部染上。
考虑一共有xxx个点在选出来的行和列里面,尝试计算把全部点染完的期望步数。首先,染完第一个点的概率是xn×m\frac{x}{n\times m}n×mx,因此期望步数为n×mx\frac{n\times m}{x}xn×m;在此之后,染完第二个点的概率是x−1n×m\frac{x-1}{n\times m}n×mx−1,因此期望步数为n×mx−1\frac{n\times m}{x-1}x−1n×m,因此类推,总的贡献就是∑i=1kn×mi\sum_{i=1}^k\frac{n\times m}{i}∑i=1kin×m。
#include <bits/stdc++.h>
#define ll long long
using namespace std;const int N = 1e6 + 10, mod = 998244353;int power(int a, int b) {int ret = 1;while (b) {if (b & 1) ret = 1ll * ret * a % mod;a = 1ll * a * a % mod; b >>= 1;}return ret;
}int n, m, k;
vector<int> a[N];
int f[N];
int fc[N], ifc[N];int C(int a, int b) {return 1ll * fc[a] * ifc[b] % mod * ifc[a - b] % mod;
}void solve() {cin >> n >> m >> k;f[1] = 1;for (int i = 2; i <= n * m; i++) {f[i] = (f[i - 1] + power(i, mod - 2)) % mod;}fc[0] = 1;for (int i = 1; i <= n * m; i++) {fc[i] = 1ll * fc[i - 1] * i % mod;}ifc[n * m] = power(fc[n * m], mod - 2);for (int i = n * m - 1; i >= 0; i--) {ifc[i] = 1ll * ifc[i + 1] * (i + 1) % mod;}for (int i = 1; i <= n; i++) {a[i].clear();a[i].resize(m + 5);}for (int i = 1; i <= k; i++) {int x, y;cin >> x >> y;a[x][y] = 1;}int s1 = 0, s2 = 0;for (int i = 1; i <= n; i++) {bool bk = 1;for (int j = 1; j <= m; j++) {if (a[i][j]) {bk = 0; break;}}if (bk) s1++;}for (int i = 1; i <= m; i++) {bool bk = 1;for (int j = 1; j <= n; j++) {if (a[j][i]) {bk = 0; break;}}if (bk) s2++;}if (!s1 && !s2) {cout << "-1\n";return;}int ans = 0;for (int i = 0; i <= s1; i++) {for (int j = 0; j <= s2; j++) {if (!i && !j) continue;int w = (i + j) % 2 ? 1 : mod - 1;int cnt = i * m + j * n - i * j;int ret = 1ll * n * m * f[cnt] % mod;ret = 1ll * ret * C(s1, i) % mod * C(s2, j) % mod;ret = 1ll * ret * w % mod;ans = (ans + ret) % mod;}}cout << ans << endl;
}int main() {// freopen("in.in", "r", stdin);ios::sync_with_stdio(false);cin.tie(nullptr);int T; cin >> T;while (T--) solve();return 0;
}
下一题是[HAOI2015] 按位或
设SSS是每一位的集合
根据前面所说的公式
E(max(S))=∑T⊆S,T≠∅E(min(T))E(\max(S))=\sum_{T\subseteq S,T\ne \empty}E(\min(T)) E(max(S))=T⊆S,T=∅∑E(min(T))
也就是要枚举每一个子集,求出其第一个出现期望有多久。
我们只需要用高维前缀和算出选择一次不包括TTT的概率ppp,那么E(min(T))=11−pE(\min(T))=\frac{1}{1-p}E(min(T))=1−p1,然后就可以求出E(max(S))E(\max(S))E(max(S))了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;const int N = (1 << 20) + 10;
const double eps = 1e-9;int n; double f[N], g[N];int main() {// freopen("in.in", "r", stdin);ios::sync_with_stdio(false);cin.tie(nullptr);cin >> n;for (int i = 0; i < (1 << n); i++) {cin >> f[i];g[i] = f[i];}for (int j = 0; j < n; j++) {bool flag = 0;for (int i = 0; i < (1 << n); i++) {if (i >> j & 1) {if (f[i] > eps) {flag = 1; break;}}}if (!flag) {cout << "INF\n";return 0;}}for (int j = 0; j < n; j++) {for (int i = 0; i < (1 << n); i++) {if (i >> j & 1) {g[i] += g[i - (1 << j)];}}}double ans = 0;for (int i = 1; i < (1 << n); i++) {double w = -1;for (int j = 0; j < n; j++) {if (i >> j & 1) w = -w;}int mask = ((1 << n) - 1) ^ i;double t = 1.0 / (1.0 - g[mask]);ans += w * t;}cout << fixed << setprecision(8) << ans << endl;
}