min-max 容斥

公式

普通 min-max 容斥:

maxiSai=TST(1)|T|1minjTaj

miniSai=TST(1)|T|1maxjTaj

扩展 min-max 容斥:

kthmaxiSai=TS|T|k(1)|T|k(|T|1k1)minjTaj

kthminiSai=TS|T|k(1)|T|k(|T|1k1)maxjTaj

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

证明

普通 min-max 容斥:

由于对称性,只需证第一条式子。考虑计算每个 ai 作为 minjTaj 的贡献系数。显然 T 中比 ai 小的数不能选,设有 r 个比 ai 大的,枚举从这 r 个数中选出多少个,则贡献系数为:

j=0r(1)j(rj)

r=0,原式 =1;当 r>0,原式 =j=0r1rj×(1)j(rj)=[1+(1)]r=0r=0,因此只有 S 中的最大值才会造成 1 的贡献。证毕。

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

例题

1. HDU4336 Card Collector

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

ai 表示 i 抽到的时间,则根据 min-max 容斥,有 E(maxiSai)=TST(1)|T|1E(minjTaj)

E(miniSai)=1iSai,于是这题就做完了。

时间复杂度 O(n2n),可优化成 O(2n)

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(maxiSai)=TST(1)|T|1E(minjTaj)

n 个变量 ai 表示第 i 位变为 1 的时间,与其求出所有位都变为 1 的期望时间 E(maxiSai),我们不如将其转化为求第一位变为 1 的期望时间 E(minjTaj)

一个集合 S 中出现 1 的概率为 1T(US)pT,期望为 11T(US)pT。做一遍子集和后即可快速计算,可以使用 SOS dp 或 FMT。

时间复杂度 O(n2n)

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。设 fS,u 表示从 u 出发且经过 S 的第一个点的期望时间。

  • 对于 uSfS,u=0
  • 对于 uSfS,u=1degu(fS,fau+vsonufS,v)+1

发现转移出现了环。高斯消元?时间复杂度过高。考虑一个套路:将 fS,u 设成 AufS,fau+Bu,然后代入化简式子。

fS,u=1degu(fS,fau+vsonufS,v)+1

fS,u=1degu(fS,fau+vsonuAvfS,u+Bv)+1

fS,u=1degu(fS,fau+(vsonuAv)fS,u+vsonuBv)+1

sumau=vsonuAvsumbu=vsonuBv,继续化简。

fS,u=1degu(fS,fau+sumaufS,u+sumbu)+1

degufS,u=fS,fau+sumaufS,u+sumbu+degu

(degusumau)fS,u=fS,fau+sumbu+degu

fS,u=1degusumaufS,fau+sumbu+degudegusumau

可以得出 Au=1degusumauBu=sumbu+degudegusumau

由于 root 不存在 father,所以 fS,root=Broot

所以对于一个点集 S,它的答案为 TST(1)|T|1fT,root。预处理所有 fS,root 再做一遍子集和,就可以 O(1) 回答单次询问。

总时间复杂度 O(n2nlogmod+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(kthminiSai) 不好算,考虑 knk+1 后转化为 kthmax,可以通过 min 来算。E(miniSai),即 S 中第一次出现原料的期望时间。不难得出 E(miniSai) = miSpi。答案为:

ans=TS|T|k(1)|T|k(|T|1k1)miTpi

如果设 dp 状态为 fi,j,k 表示前 i 个物品组成 |T|=jiTai=k 的方案数,则时间复杂度为 O(nm2),无法接受。考虑省掉一维,直接设 fi,j 表示前 i 个物品组成 iTpi=j 的系数 (1)|T|k(|T|1k1) 之和。当 |T| 不变时,fi,jfi1,j;当 T 增加 1(1)(|T|+1)k((|T|+1)1k1)=(1)|T|k(|T|1k1)+(1)|T|(k1)(|T|1k2)。dp 中可以再增加一维 k 表示当前的 k,则 fi,j,kfi1,jpi,k+fi1,jpi,k1

dp 的初始状态为 f0,0,0=1,即当 i=0|T|=0k=0(1)|T|k(|T|1k1)=(11)=1,对于其他 k0

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

总时间复杂度为 O(nm(nk))

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 @   zltzlt  阅读(268)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示