咱們知道對於普通的 \(\min-\max\) 容斥有以下式子:
\[ \max(S) = \sum_{T \subseteq S} (-1)^{|T| + 1} \min(T)\\ \min(S) = \sum_{T \subseteq S} (-1)^{|T| + 1} \max(T) \]html
證實能夠構造一一映射,抵消貢獻。c++
上述式子在指望意義下也是成立的:
\[ E[\max(S)] = \sum_{T \subseteq S} (-1)^{|T| + 1} E[\min(T)] \]git
證實能夠考慮指望的線性性。函數
本文參考了 張俊逸同窗 的集訓講解。spa
咱們能夠推廣到求第 \(k\) 大元素上來。debug
咱們嘗試構造容斥係數 \(f\) ,知足:
\[ kth \max(S) = \sum_{T \subseteq S} f_{|T|} \min(T) \]code
而後來考慮一下對於集合 \(T\) 中第 \(i\) 大的元素,若是 \(\min(T)\) 等於這個元素,那麼只有比它大的 \(i-1\) 個元素是可能存在的,而後咱們考慮 \(T\) 何時才能對於答案進行貢獻,其實就是要知足 \([i = k]\) 。htm
那麼表達出來就是
\[ \sum_{j = 1}^{i - 1} {i - 1 \choose j} f_{j + 1} = [i = k] \]blog
這個形式咱們能夠套用二項式反演(廣義容斥定理)get
原來我博客說起的都是從 \(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}\) 。
那麼根據二項式反演就能夠獲得
\[ f'_{i} = \sum_{j = 0}^{i} (-1)^{i - j} {i \choose j} g_j \]
即
\[ f_{i + 1} = (-1)^{i - (k - 1)} {i \choose k - 1} \]
隨意整理一下就有
\[ f_i = (-1)^{i - k} {i - 1 \choose k - 1} \]
最終咱們就能夠以下求 \(kth \max\)
\[ kth \max(S) = \sum_{T \subseteq S} (-1)^{|T| - k} {|T| - 1 \choose k - 1} \min(T) \]
不難發現只有元素個數 \(\ge k\) 的子集是有用的。
給定集合 \(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)\) 。
參考 此處 就能夠獲得
\[ E(T) = \frac{1}{\sum_{i \in T} \frac{p_i}{m}} \]
整理一下
\[ E(T) = \frac{m}{\sum_{i \in T} p_i} \]
若是 \(|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; }