min-max 容斥
公式
普通 min-max 容斥:
扩展 min-max 容斥:
上述式子在期望下也成立。
证明
普通 min-max 容斥:
由于对称性,只需证第一条式子。考虑计算每个 \(a_i\) 作为 \(\min\limits_{j \in T} a_j\) 的贡献系数。显然 \(T\) 中比 \(a_i\) 小的数不能选,设有 \(r\) 个比 \(a_i\) 大的,枚举从这 \(r\) 个数中选出多少个,则贡献系数为:
当 \(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\),然后代入化简式子。
令 \(suma_u = \sum\limits_{v \in son_u} A_v\),\(sumb_u = \sum\limits_{v \in son_u} B_v\),继续化简。
可以得出 \(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}\)。答案为:
如果设 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;
}

浙公网安备 33010602011771号