「2020-2021 集训队作业」Gem Island 2(二项式反演,组合数递推)

复盘去年 \(\color{black}{\text{4}}\color{red}{\text{182_543_731}}\) 讲的题,彻底感受到了大佬的强大!!1

Description

给定 \(n\) 个位置,每个位置初始值为 \(1\) ,给定 \(m\) 为可操作数,每次操作等概率选择一个位置数值 \(+ 1\) ,给定 \(k\) ,求数值前 \(k\) 大的总和期望值。

弱化版: \(1\leq n,\ m \leq 500,\ k \leq n\)

这道题: \(1\leq n,\ m \leq 1.5 \cdot 10 ^ 7,\ k \leq n\)

Analysis

\(m\) 或者 \(k\) 作为切入口会因为可能性太多计不来数,所以我们要勇于把枚举的数值作为切入口!!1

提前声明,根据题目有:

\[\sum_i a_i = n + m,\ a \geq 1 \]

我们都知道有这么一个东西:

\[a = \sum_{i = 1} ^ {\infty} [i \leq a] \]

所以我们考虑计数,可以先想办法求这样一个东西:设 \(F_{a, i}\) 表示有 \(i\) 个位置里面数值大于 \(a\) 的方案数。

转化一下说法,恰好 \(i\) 个位置,一眼二项式反演,设 \(G_{a, i}\) 表示钦定了 \(i\) 个位置里面数值大于等于 \(a\) 的方案数。

\(G\) 是一个比较好求的东西,直接考虑意义,先从 \(n\) 个位置选择 \(i\) 个位置,其次利用插板法,题目要求 \(a \geq 1\) 的,所以我们从总数量中先剔出 \((i - 1) \cdot a\) ,然后在剩下的数里面放入 \(n - 1\) 个插板:

\[G_{a, i} = \binom{n}{i} \binom{m + n - (a - 1) \cdot i - 1}{n - 1} \]

反演的两个式子都是最基本的:

\[G_{a, i} = \sum_{j = i} ^ {n} \binom{j}{i} F_{a, j} \]

\[F_{a, i} = \sum_{j = i} ^ {n} \binom{j}{i} (-1) ^ {j - i} G_{a, j} \]

然后对于每一个 \(a\) 都有贡献:

\[\sum_i F_{a, i} \cdot \min(i, k) \]

取较小值是题意,而对于每种 \(a\) ,考虑到原先那个式子,每一个小于等于 \(a\) 的状态都能贡献一个 \(1\) ,总和就是 \(a\)

这玩意硬要做是能做到 \(O(n ^ 2)\) 的,不过不能通过此题,所以必须继续推。

Solution

分析就放 \(O(n ^ 2)\) 做法属实有点尴尬。

我们能明显感觉到二项式反演已经帮了我们很大的忙,但是终究不够简洁,考虑暂时放弃。

组合意义灭天地,代数推导保平安。 ————tiger0133

我们考虑直接对 \(G\) 下手,直接利用反演的式子代换 \(F\)

\[\sum_i \bigg( \sum_{j = i} ^ n G_{a, j} \binom{j}{i} (-1) ^ {j - i} \min(i, k) \bigg) \]

更换一下 \(G\) 枚举的东西。

\[\sum_j G_{a, j} \bigg( \sum_{j \geq i} \binom{j}{i} (-1) ^ {j - i} \min(i, k) \bigg) \]

因为 \(G\) 是可以秒算的,所以我们考虑只去计算后面的系数。

因为形式比较复杂,我们不太可能从组合意义的角度分析,所以有可能需要递推计算。

我们令:

\[v_{n, k} = \sum_{n \geq i} \binom{n}{i} (-1) ^ {n - i} \min(i, k) \]

发现并没有什么比较直接的方法能够化简,不过我们都知道一个很经典的式子 \(\binom{n}{m} = \binom{n - 1}{m - 1} + \binom{n - 1}{m}\) ,来试试!

\[v_{n, k} = \sum_{n \geq i} \binom{n - 1}{i} (-1) ^ {n - i} \min(i, k) + \sum_{n \geq i} \binom{n - 1}{i - 1} (-1) ^ {n - i} \min(i, k) \]

然后我们需要把这些转换成类似原先定义式的形式:

\[v_{n, k} = \sum_{n \geq i} \binom{n - 1}{i} (-1)(-1) ^ {(n - 1) - i} \min(i, k) + \sum_{n \geq i} \binom{n - 1}{i - 1} (-1) ^ {(n - 1) - (i - 1)} \Big( 1 + \min(i - 1, k - 1) \Big) \]

\[v_{n, k} = -\sum_{(n - 1) \geq i} \binom{n - 1}{i} (-1) ^ {(n - 1) - i} \min(i, k) + \sum_{(n - 1) \geq (i - 1)} \binom{n - 1}{i - 1} (-1) ^ {(n - 1) - (i - 1)} \min(i - 1, k - 1) + \sum_{n \geq i} \binom{n - 1}{i - 1} (-1) ^ {(n - 1) - (i - 1)} \]

很明显前面两截已经吻合定义式了,同时后面这个可以视作组合数的一个公式:

\[\sum_{i = 0} ^ n \binom{n}{i} (-1) ^ {i} = [n = 0] \]

所以就有:

\[v_{n, k} = -v_{n - 1, k} + v_{n - 1, k - 1} + [n - 1 = 0] \]

\[v_{n, k} = -v_{n - 1, k} + v_{n - 1, k - 1} + [n = 1] \]

显然根据定义式所有 \(n=0\) 的地方值都是 \(0\) ,可以忽略不计,所以就有:

边界: \(v_{1, k} = 1\) ;递推式: \(v_{n, k} = -v_{n - 1, k} + v_{n - 1, k - 1} (n > 1)\)

够简洁,但是不够直接,因为这似乎对我们的实际计算并没有什么太大的用处。

考虑组合意义,说说我自己的想法,我们可以把 \(n\) 看作集合内的元素总数, \(k\) 为选择的数量。

当两者同时减一时符号不变,反之变号,可以看作考虑剩下元素的奇偶性,添加 \((-1)\) 调整,所以有:

\[v_{n, k} = \sum_{i = 0} ^ {k - 1} (-1) ^ {n - 1 - i} \binom{n - 1}{i} \]

欸,又得考虑递推了,不过还是老办法:

\[v_{n, k} = \sum_{i = 0} ^ {k - 1} (-1) ^ {(n - 1) - i} \binom{n - 2}{i} + \sum_{i = 0} ^ {k - 1} (-1) ^ {(n - 1) - i} \binom{n - 2}{i - 1} \]

\[v_{n, k} = -\sum_{i = 0} ^ {k - 1} (-1) ^ {(n - 2) - i} \binom{n - 2}{i} + \sum_{i = 0} ^ {k - 1} (-1) ^ {(n - 2) - (i - 1)} \binom{n - 2}{i - 1} \]

显然后半截是可能出现负数下标的,更换一下,整体左移:

\[v_{n, k} = -\sum_{i = 0} ^ {k - 1} (-1) ^ {(n - 2) - i} \binom{n - 2}{i} + \sum_{i = 0} ^ {k - 2} (-1) ^ {(n - 2) - i} \binom{n - 2}{i} \]

然后你发现发部分东西都被消掉了:

\[v_{n, k} = -(-1) ^ {(n - 2) - (k - 1)} \binom{n - 2}{k - 1} \]

\[v_{n, k} = (-1) ^ {n - k} \binom{n - 2}{k - 1} \]

现在就是这样了:

边界: \(v_{1, k} = 1\) ;公式: \(v_{n, k} = (-1) ^ {n - k} \binom{n - 2}{k - 1} (n > 1)\)

所以说现在我们再来考虑答案:

\[n + \sum_{a > 0} \sum_{i = 1} ^ {n} G_{a, i} v_{i, k} \]

要注意一下这里的 \(k\) 应当是 \(\min(i, k)\) 。因为本身 \(i\) 就是作为答案出现,所以不能超过 \(k\)

\[n + \sum_{a > 0} n \binom{m + n - a}{n - 1} + \sum_{a > 0} \sum_{i = 2} ^ {n} \binom{n}{i} \binom{m + n - (a - 1) \cdot i - 1}{n - 1} (-1) ^ {i - k} \binom{i - 2}{k - 1} \]

前面两截都可以快速算得,但是最后一个比较麻烦。因为好像可以看到类似因数求和得形式,可以考虑更换枚举顺序。

\[\sum_{t} \binom{m + n - t - 1}{n - 1} \sum_{t | i} \binom{n}{i} (-1) ^ {i - k} \binom{i - 2}{k - 1} \]

再换一下:

\[\sum_{i = 2} ^ {n} \binom{n}{i} (-1) ^ {i - k} \binom{i - 2}{k - 1} \sum_{t | i} \binom{m + n - t - 1}{n - 1} \]

现在我们可以对于每一个 \(i\) 对最后一坨提前预处理。

明显地看到这是一个类似埃氏筛的东西,处理质数可以做到 \(O(n\log\log n)\) 处理,剩下的计算都是 \(O(n)\) 的,就能过题了。

最后的最后,我们只计算了方案数的答案贡献,所以要除掉总方案数 \(\binom{n + m - 1}{n - 1}\)

Code

Code

/*

*/
#include 
using namespace std;

#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);

typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair

const int mod = 998244353;
template 
inline int M(A x) {return x;}
template 
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}

#define mi(x) (x >= mod) && (x -= mod)
#define ad(x) (x < 0) && (x += mod)

const int N = 1.5e7 + 7, P = 1e6;

int n, m, k, fac[N << 1], inv[N << 1];

inline int ksm(int a, int b) {
	int tm = 1;
	for (; b; b >>= 1, a = M(a, a)) b & 1 && (tm = M(tm, a));
	return tm;
}

inline int C(int n, int m) {return n < m || m < 0 ? 0 : M(fac[n], inv[m], inv[n - m]);}

int pri[P], tot, xs[N];
bool vi[N];

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	std::cin >> n >> m >> k;
	fac[0] = inv[0] = 1;
	for (int i = 1; i <= n + m; ++i) fac[i] = M(fac[i - 1], i);
	inv[n + m] = ksm(fac[n + m], mod - 2);
	for (int i = n + m - 1; i; --i) inv[i] = M(inv[i + 1], i + 1);

	xs[1] = C(n + m - 2, n - 1);
	for (int i = 2; i <= m; ++i) {
		!vi[i] && (pri[++tot] = i);

		for (int j = 1; j <= tot && pri[j] * i <= m; ++j) {
			vi[i * pri[j]] = 1;
			if (!(i % pri[j])) break;
		}

		xs[i] = C(n + m - i - 1, n - 1);
	}

	for (int j = 1; j <= tot; ++j) {
		for (int i = m / pri[j]; i >= 1; --i) xs[i] += xs[i * pri[j]], mi(xs[i]);
	}

	int cb = C(n + m - 1, n - 1), ans = M(n, xs[1] + cb);
	for (int i = 2; i <= n; ++i) {
		int nu = std::min(i, k), ret = M(C(n, i), xs[i] + cb, C(i - 2, nu - 1));
		if ((i ^ nu) & 1) ans -= ret, ad(ans); else ans += ret, mi(ans);
	}

	std::cout << M(ksm(cb, mod - 2), ans) << "\n";

    return 0;
}

posted @ 2022-08-19 10:52  Illusory_dimes  阅读(151)  评论(1编辑  收藏  举报