min-max 容斥
公式
普通 min-max 容斥:
扩展 min-max 容斥:
上述式子在期望下也成立。
证明
普通 min-max 容斥:
由于对称性,只需证第一条式子。考虑计算每个 作为 的贡献系数。显然 中比 小的数不能选,设有 个比 大的,枚举从这 个数中选出多少个,则贡献系数为:
当 ,原式 ;当 ,原式 ,因此只有 中的最大值才会造成 的贡献。证毕。
扩展 min-max 容斥不会证,先咕着。
例题
1. HDU4336 Card Collector
这题 dp 也能做,但是 min-max 容斥的解法更加优美。
设 表示 抽到的时间,则根据 min-max 容斥,有 。
,于是这题就做完了。
时间复杂度 ,可优化成 。
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 容斥,有 。
设 个变量 表示第 位变为 的时间,与其求出所有位都变为 的期望时间 ,我们不如将其转化为求第一位变为 的期望时间 。
一个集合 中出现 的概率为 ,期望为 。做一遍子集和后即可快速计算,可以使用 SOS dp 或 FMT。
时间复杂度 。
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 容斥,可以将 中的所有点都经过一次的期望时间转化为到达 中的第一个点的期望时间。
考虑 dp。设 表示从 出发且经过 的第一个点的期望时间。
- 对于 ,。
- 对于 ,。
发现转移出现了环。高斯消元?时间复杂度过高。考虑一个套路:将 设成 ,然后代入化简式子。
令 ,,继续化简。
可以得出 ,。
由于 不存在 father,所以 。
所以对于一个点集 ,它的答案为 。预处理所有 再做一遍子集和,就可以 回答单次询问。
总时间复杂度 。
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 的神题。 不好算,考虑 后转化为 ,可以通过 来算。,即 中第一次出现原料的期望时间。不难得出 = 。答案为:
如果设 dp 状态为 表示前 个物品组成 且 的方案数,则时间复杂度为 ,无法接受。考虑省掉一维,直接设 表示前 个物品组成 的系数 之和。当 不变时,;当 增加 ,。dp 中可以再增加一维 表示当前的 ,则 。
dp 的初始状态为 ,即当 ,,,,对于其他 为 。
由于本题卡空间,所以要用滚动数组优化。
总时间复杂度为 。
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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话