Kth MIN-MAX 反演
MIN-MAX 反演
我们知道对于普通的 \(\min-\max\) 容斥有如下式子:
证明可以构造一一映射,抵消贡献。
上述式子在期望意义下也是成立的:
证明可以考虑期望的线性性。
Kth MIN-MAX 反演
本文参考了 张俊逸同学 的集训讲解。
构造容斥系数
我们可以推广到求第 \(k\) 大元素上来。
我们尝试构造容斥系数 \(f\) ,满足:
然后来考虑一下对于集合 \(T\) 中第 \(i\) 大的元素,如果 \(\min(T)\) 等于这个元素,那么只有比它大的 \(i-1\) 个元素是可能存在的,然后我们考虑 \(T\) 什么时候才能对于答案进行贡献,其实就是要满足 \([i = k]\) 。
那么表达出来就是
二项式反演
这个形式我们可以套用二项式反演(广义容斥定理)
原来我博客提及的都是从 \(i = k \to n\) 的,其实 \(i = 1 \to k\) 也是一样的。
二项式反演:
假设数列 \(f\) 和 \(g\) 满足:
\[g_i = \sum_{j = 1}^{i} {i \choose j} f_j \]那么就有
\[f_i = \sum_{j = 1}^{i} (-1)^{i - j} {i \choose j} g_j \]可以考虑生成函数证明,设 \(f_0 = g_0 = 0\) 。
前者就为 \(G = F * e^x\) 后者为 \(F = G * e^{-x}\) ,显然是等价的。
那么对于前面那个式子,我们设 \(g_{i - 1} = [i = k], f'_j = f_{j + 1}\) 。
那么根据二项式反演就可以得到
即
随意整理一下就有
结论
最终我们就可以如下求 \(kth \max\)
不难发现只有元素个数 \(\ge k\) 的子集是有用的。
例题
Luogu P4707 重返现世
题意
给定集合 \(S\) 中每个元素出现的概率 \(\displaystyle \frac{p_i}{m}\) ,其和为\(1\) ,每次会按概率出现,每次会按概率出现一个元素。
求出现 \(k\) 个元素的期望次数。
设 \(|S| = n\) 满足限制
\(n \le 1000, \left| n - k \right| \le 10, 1 \le \sum p = m \le 10000\)
题解
出现 \(k\) 个元素的期望次数等价于出现时间第 \(n-k+1\) 晚的数出现的期望次数。
那么套用 \(kth \max\) 容斥后,我们相当于要求每个集合出现最早的数的期望次数 \(E(T)\) 。
参考 此处 就可以得到
整理一下
如果 \(|S| \le 20\) 那么就可以直接做了,可是这题数据范围有点大,不太好做。
但是我们发现 \(m\) 不大,那么我们可以对于每个 \(\sum_{i \in T} p_i\) 求得其系数解决。
设 \(g_{i, j}\) 为集合大小为 \(i\) ,概率和为 \(j\) 的方案数,但是这样的话直接 \(dp\) 复杂度是 \(O(nmk)\) 的,我们显然不能这样做。
我们需要把容斥系数也要考虑到一起 \(dp\) 。
设 \(f_{i, j}\) 表示当前选定的集合的 \(p\) 的和为 \(i\) ,组合数下面那里 \(k=j\) 。
对于当前物品而言,只有两种选择:要么加入集合,要么不加入。
-
不加入,直接转上去 \(f_{i, j} \to f'_{i, j}\) 。
-
加入这个元素,考虑其贡献。
\(f\) 的形式为 \(\displaystyle f_{j - p, k - 1} = \sum_i (-1)^{i - (k - 1)} {i - 1 \choose k - 2} g_{i, j - p}\) 。
那么就会导致集合大小强制多 \(1\) ,并且概率和变成 \(j\) ,那么就是 \(\displaystyle \sum_i (-1)^{i-k}{i\choose k-1} g_{i, j - p}\) 。
接下来 强行 凑组合数递推形式 \(\displaystyle {n\choose m}={n-1\choose m}+{n-1\choose m-1}\) ,把 \(\displaystyle {i \choose k - 1}\) 变为 \(\displaystyle {i \choose k}\)
那么我们要减去的其实就是 \(\displaystyle -\sum(-1)^{i-k}{i-1\choose k-1} g_{i, j - p}\) ,其实就是 \(- f_{j - p, k}\) 。
那么最后的转移就是 \(f'_{i,j} + f'_{i - p, j - 1} - f'_{i - p, j} \to f_{i, j}\) 。
注意要滚动第一维,不然开不下。
复杂度是 \(O(n(n - k)m)\) ,还是很暴力。
后面这些强行套的地方好没有意思啊!
代码
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("P4707.in", "r", stdin);
freopen ("P4707.out", "w", stdout);
#endif
}
const int N = 1010, Mod = 998244353;
int n, K, m, dp[2][10010][13];
inline int fpm(int x, int power) {
int res = 1;
for (; power; power >>= 1, x = 1ll * x * x % Mod)
if (power & 1) res = 1ll * res * x % Mod;
return res;
}
inline void Add(int &x, int y) {
if ((x += y) >= Mod) x -= Mod;
}
int main () {
File();
n = read(); K = n - read() + 1; m = read();
For (i, 1, K) dp[0][0][i] = Mod - 1;
int cur = 0;
For (i, 1, n) {
int p = read();
For (j, 0, m) For (k, 0, K) if (dp[cur][j][k]) {
Add(dp[cur ^ 1][j][k], dp[cur][j][k]);
Add(dp[cur ^ 1][j + p][k + 1], dp[cur][j][k]);
Add(dp[cur ^ 1][j + p][k], Mod - dp[cur][j][k]);
dp[cur][j][k] = 0;
}
cur ^= 1;
}
int ans = 0;
For (i, 1, m)
ans = (ans + 1ll * m * fpm(i, Mod - 2) % Mod * dp[cur][i][K]) % Mod;
printf ("%d\n", ans);
return 0;
}