min-max 容斥

公式

普通 min-max 容斥:

\[\max\limits_{i \in S} a_i = \sum\limits_{T \subseteq S \and T \ne \varnothing} (-1)^{|T|-1} \min\limits_{j \in T} a_j \]

\[\min\limits_{i \in S} a_i = \sum\limits_{T \subseteq S \and T \ne \varnothing} (-1)^{|T|-1} \max\limits_{j \in T} a_j \]

扩展 min-max 容斥:

\[\operatorname{kthmax}\limits_{i \in S} a_i = \sum\limits_{T \subseteq S \and |T| \ge k} (-1)^{|T|-k}\dbinom{|T|-1}{k-1} \min\limits_{j \in T} a_j \]

\[\operatorname{kthmin}\limits_{i \in S} a_i = \sum\limits_{T \subseteq S \and |T| \ge k} (-1)^{|T|-k}\dbinom{|T|-1}{k-1} \max\limits_{j \in T} a_j \]

上述式子在期望下也成立。

证明

普通 min-max 容斥:

由于对称性,只需证第一条式子。考虑计算每个 \(a_i\) 作为 \(\min\limits_{j \in T} a_j\) 的贡献系数。显然 \(T\) 中比 \(a_i\) 小的数不能选,设有 \(r\) 个比 \(a_i\) 大的,枚举从这 \(r\) 个数中选出多少个,则贡献系数为:

\[\sum\limits_{j=0}^r (-1)^j \dbinom{r}{j} \]

\(r = 0\),原式 \(= 1\);当 \(r > 0\),原式 \(= \sum\limits_{j=0}^r 1^{r-j} \times (-1)^j \dbinom{r}{j} = [1 + (-1)]^r = 0^r = 0\),因此只有 \(S\) 中的最大值才会造成 \(1\) 的贡献。证毕。

扩展 min-max 容斥不会证,先咕着。

例题

1. HDU4336 Card Collector

这题 dp 也能做,但是 min-max 容斥的解法更加优美。

\(a_i\) 表示 \(i\) 抽到的时间,则根据 min-max 容斥,有 \(E(\max\limits_{i \in S} a_i) = \sum\limits_{T \subseteq S \and T \ne \varnothing} (-1)^{|T|-1} E(\min\limits_{j \in T} a_j)\)

\(E(\min\limits_{i \in S} a_i) = \dfrac{1}{\sum\limits_{i \in S} a_i}\),于是这题就做完了。

时间复杂度 \(O(n2^n)\),可优化成 \(O(2^n)\)

code
/*

p_b_p_b txdy
AThousandSuns txdy
Wu_Ren txdy
Appleblue17 txdy

*/

#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;

const int maxn = 22;
const int maxm = (1 << 20) + 100;

int n;
double p[maxn];

void solve() {
	for (int i = 0; i < n; ++i) {
		scanf("%lf", &p[i]);
	}
	double ans = 0;
	for (int S = 1; S < (1 << n); ++S) {
		double coef = -1, sum = 0;
		for (int i = 0; i < n; ++i) {
			if (S & (1 << i)) {
				coef *= -1;
				sum += p[i];
			}
		}
		ans += coef / sum;
	}
	printf("%.5lf\n", ans);
}

int main() {
	// int T = 1;
	// scanf("%d", &T);
	while (scanf("%d", &n) == 1) {
		solve();
	}
	return 0;
}

2. 洛谷 P3175 [HAOI2015]按位或

根据 min-max 容斥,有 \(E(\max\limits_{i \in S} a_i) = \sum\limits_{T \subseteq S \and T \ne \varnothing} (-1)^{|T|-1} E(\min\limits_{j \in T} a_j)\)

\(n\) 个变量 \(a_i\) 表示第 \(i\) 位变为 \(1\) 的时间,与其求出所有位都变为 \(1\) 的期望时间 \(E(\max\limits_{i \in S} a_i)\),我们不如将其转化为求第一位变为 \(1\) 的期望时间 \(E(\min\limits_{j \in T} a_j)\)

一个集合 \(S\) 中出现 \(1\) 的概率为 \(1 - \sum\limits_{T \subseteq (U \setminus S)} p_T\),期望为 \(\dfrac{1}{1 - \sum\limits_{T \subseteq (U \setminus S)} p_T}\)。做一遍子集和后即可快速计算,可以使用 SOS dp 或 FMT。

时间复杂度 \(O(n2^n)\)

code
/*

p_b_p_b txdy
AThousandSuns txdy
Wu_Ren txdy
Appleblue17 txdy

*/

#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;

const int maxn = (1 << 20) + 100;
const ldb EPS = 1e-10;

int n, m;
ldb a[maxn];

void solve() {
	scanf("%d", &m);
	n = (1 << m);
	for (int i = 0; i < n; ++i) {
		scanf("%Lf", &a[i]);
	}
	for (int i = 0; i < m; ++i) {
		for (int j = 0; j < n; ++j) {
			if (j & (1 << i)) {
				a[j] += a[j ^ (1 << i)];
			}
		}
	}
	ldb ans = 0;
	for (int i = 1; i < n; ++i) {
		if (fabs(1 - a[(n - 1) ^ i]) < EPS) {
			puts("INF");
			return;
		}
		ldb coef = -1;
		for (int j = 0; j < m; ++j) {
			if (i & (1 << j)) {
				coef *= -1;
			}
		}
		ans += coef / (1 - a[(n - 1) ^ i]);
	}
	printf("%.10Lf\n", ans);
}

int main() {
	int T = 1;
	// scanf("%d", &T);
	while (T--) {
		solve();
	}
	return 0;
}

3. 洛谷 P5643 [PKUWC2018]随机游走

根据 min-max 容斥,可以将 \(S\) 中的所有点都经过一次的期望时间转化为到达 \(S\) 中的第一个点的期望时间。

考虑 dp。设 \(f_{S,u}\) 表示从 \(u\) 出发且经过 \(S\) 的第一个点的期望时间。

  • 对于 \(u \in S\)\(f_{S,u} = 0\)
  • 对于 \(u \notin S\)\(f_{S,u} = \dfrac{1}{deg_u} (f_{S,fa_u} + \sum\limits_{v \in son_u} f_{S,v}) + 1\)

发现转移出现了环。高斯消元?时间复杂度过高。考虑一个套路:将 \(f_{S,u}\) 设成 \(A_u f_{S,fa_u} + B_u\),然后代入化简式子。

\[f_{S,u} = \dfrac{1}{deg_u} (f_{S,fa_u} + \sum\limits_{v \in son_u} f_{S,v}) + 1 \]

\[f_{S,u} = \dfrac{1}{deg_u} (f_{S,fa_u} + \sum\limits_{v \in son_u} A_v f_{S,u} + B_v) + 1 \]

\[f_{S,u} = \dfrac{1}{deg_u} (f_{S,fa_u} + (\sum\limits_{v \in son_u} A_v) f_{S,u} + \sum\limits_{v \in son_u} B_v) + 1 \]

\(suma_u = \sum\limits_{v \in son_u} A_v\)\(sumb_u = \sum\limits_{v \in son_u} B_v\),继续化简。

\[f_{S,u} = \dfrac{1}{deg_u} (f_{S,fa_u} + suma_u f_{S,u} + sumb_u) + 1 \]

\[deg_u f_{S,u} = f_{S,fa_u} + suma_u f_{S,u} + sumb_u + deg_u \]

\[(deg_u - suma_u) f_{S,u} = f_{S,fa_u} + sumb_u + deg_u \]

\[f_{S,u} = \dfrac{1}{deg_u - suma_u} f_{S,fa_u} + \dfrac{sumb_u + deg_u}{deg_u - suma_u} \]

可以得出 \(A_u = \dfrac{1}{deg_u - suma_u}\)\(B_u = \dfrac{sumb_u + deg_u}{deg_u - suma_u}\)

由于 \(root\) 不存在 father,所以 \(f_{S,root} = B_{root}\)

所以对于一个点集 \(S\),它的答案为 \(\sum\limits_{T \subseteq S \and T \ne \varnothing} (-1)^{|T|-1} f_{T,root}\)。预处理所有 \(f_{S,root}\) 再做一遍子集和,就可以 \(O(1)\) 回答单次询问。

总时间复杂度 \(O(n2^n \log mod + q)\)

code
/*

p_b_p_b txdy
AThousandSuns txdy
Wu_Ren txdy
Appleblue17 txdy

*/

#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;

const int maxn = 20;
const int maxm = (1 << 18) + 50;
const ll mod = 998244353;

ll qpow(ll b, ll p) {
	ll res = 1;
	while (p) {
		if (p & 1) {
			res = res * b % mod;
		}
		b = b * b % mod;
		p >>= 1;
	}
	return res;
}

int n, m, q, rt, head[maxn], len;
ll a[maxn], b[maxn], deg[maxn], f[maxm];

struct edge {
	int to, next;
} edges[maxn << 1];

void add_edge(int u, int v) {
	edges[++len].to = v;
	edges[len].next = head[u];
	head[u] = len;
}

void dfs(int u, int S, int fa) {
	if (S & (1 << (u - 1))) {
		return;
	}
	ll suma = 0, sumb = 0;
	for (int i = head[u]; i; i = edges[i].next) {
		int v = edges[i].to;
		if (v == fa) {
			continue;
		}
		dfs(v, S, u);
		suma = (suma + a[v]) % mod;
		sumb = (sumb + b[v]) % mod;
	}
	ll inv = qpow((deg[u] - suma + mod) % mod, mod - 2);
	a[u] = inv;
	b[u] = inv * (sumb + deg[u]) % mod;
}

void solve() {
	scanf("%d%d%d", &n, &q, &rt);
	m = (1 << n);
	for (int i = 1, u, v; i < n; ++i) {
		scanf("%d%d", &u, &v);
		add_edge(u, v);
		add_edge(v, u);
		++deg[u];
		++deg[v];
	}
	for (int S = 1; S < m; ++S) {
		mems(a, 0);
		mems(b, 0);
		dfs(rt, S, -1);
		int cnt = 0;
		for (int i = 0; i < n; ++i) {
			if (S & (1 << i)) {
				++cnt;
			}
		}
		f[S] = ((cnt & 1) ? 1 : mod - 1) * b[rt] % mod;
	}
	for (int i = 0; i < n; ++i) {
		for (int S = 0; S < m; ++S) {
			if (S & (1 << i)) {
				f[S] = (f[S] + f[S ^ (1 << i)]) % mod;
			}
		}
	}
	while (q--) {
		int k, S = 0;
		scanf("%d", &k);
		while (k--) {
			int x;
			scanf("%d", &x);
			S |= (1 << (x - 1));
		}
		printf("%lld\n", f[S]);
	}
}

int main() {
	int T = 1;
	// scanf("%d", &T);
	while (T--) {
		solve();
	}
	return 0;
}

4. 洛谷 P4707 重返现世

扩展 min-max 容斥+dp 的神题。\(E(\operatorname{kthmin}\limits_{i \in S} a_i)\) 不好算,考虑 \(k \gets n - k + 1\) 后转化为 \(\operatorname{kthmax}\),可以通过 \(\min\) 来算。\(E(\min\limits_{i \in S} a_i)\),即 \(S\) 中第一次出现原料的期望时间。不难得出 \(E(\min\limits_{i \in S} a_i)\) = \(\dfrac{m}{\sum\limits_{i \in S} p_i}\)。答案为:

\[ans = \sum\limits_{T \subseteq S \and |T| \ge k} (-1)^{|T|-k} \dbinom{|T|-1}{k-1} \dfrac{m}{\sum\limits_{i \in T} p_i} \]

如果设 dp 状态为 \(f_{i,j,k}\) 表示前 \(i\) 个物品组成 \(|T|=j\)\(\sum\limits_{i \in T} a_i = k\) 的方案数,则时间复杂度为 \(O(nm^2)\),无法接受。考虑省掉一维,直接设 \(f_{i,j}\) 表示前 \(i\) 个物品组成 \(\sum\limits_{i \in T}p_i = j\) 的系数 \((-1)^{|T|-k}\dbinom{|T|-1}{k-1}\) 之和。当 \(|T|\) 不变时,\(f_{i,j} \gets f_{i-1,j}\);当 \(T\) 增加 \(1\)\((-1)^{(|T|+1)-k} \dbinom{(|T|+1)-1}{k-1} = -(-1)^{|T|-k} \dbinom{|T|-1}{k-1} + (-1)^{|T|-(k-1)} \dbinom{|T|-1}{k-2}\)。dp 中可以再增加一维 \(k\) 表示当前的 \(k\),则 \(f_{i,j,k} \gets -f_{i-1,j-p_i,k} + f_{i-1,j-p_i,k-1}\)

dp 的初始状态为 \(f_{0,0,0} = 1\),即当 \(i = 0\)\(|T| = 0\)\(k = 0\)\((-1)^{|T|-k}\dbinom{|T|-1}{k-1} = \dbinom{-1}{-1} = 1\),对于其他 \(k\)\(0\)

由于本题卡空间,所以要用滚动数组优化。

总时间复杂度为 \(O(nm(n-k))\)

code
/*

p_b_p_b txdy
AThousandSuns txdy
Wu_Ren txdy
Appleblue17 txdy

*/

#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;

const int maxn = 1010;
const ll mod = 998244353;

ll n, m, K, a[maxn], inv[maxn * 10], f[2][maxn * 10][12];

void solve() {
	scanf("%lld%lld%lld", &n, &K, &m);
	K = n - K + 1;
	for (int i = 1; i <= n; ++i) {
		scanf("%lld", &a[i]);
	}
	inv[1] = 1;
	for (int i = 2; i <= m; ++i) {
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	}
	f[0][0][0] = 1;
	for (int i = 1, o = 1; i <= n; ++i, o ^= 1) {
		mems(f[o], 0);
		f[o][0][0] = 1;
		for (int j = 1; j <= m; ++j) {
			for (int k = 1; k <= K; ++k) {
				f[o][j][k] = f[o ^ 1][j][k];
				if (j >= a[i]) {
					f[o][j][k] = (f[o][j][k] - f[o ^ 1][j - a[i]][k] + f[o ^ 1][j - a[i]][k - 1] + mod) % mod;
				}
			}
		}
	}
	ll ans = 0;
	for (int i = 1; i <= m; ++i) {
		ans = (ans + f[n & 1][i][K] * m % mod * inv[i] % mod) % mod;
	}
	printf("%lld\n", ans);
}

int main() {
	int T = 1;
	// scanf("%d", &T);
	while (T--) {
		solve();
	}
	return 0;
}

参考资料

  1. Alex_Wei - 组合数学相关 *4. Min-Max 容斥
posted @ 2022-11-16 11:31  zltzlt  阅读(328)  评论(0)    收藏  举报