「TJOI / HEOI2016」求和 的一個優秀線性做法


我們把\(S(i, j)j!\)看成是把\(i\)個球每次選擇一些球(不能為空)扔掉,選\(j\)次后把所有球都扔掉的情況數(順序有關)。因此\(S(i, j)j! = i![x^i](e^x - 1)^j\)

為了求出答案,我們需要研究如下的生成函數的性質。

\(P(x) = \sum_{i = 0}^{n}(2e^x - 2)^i = \sum_{i = 0}^{n} 2^i \sum_{j = 0}^{i} (-1)^{i - j}e^{jx} {i \choose j} = \sum_{j = 0}^{n} e^{jx}\sum_{i = j}^{n} 2^i(-1)^{i - j} {i \choose j}\)

\(a_j = \sum_{i = j}^{n} (-2)^i {i \choose j}\)。在線性時間內計算\(a_j\)是個經典的問題。

\(a_0\)是很容易計算的。

\(j \ge 1\)時:

\(a_j\)

\(= \sum_{i = j}^{n} (-2)^i ({i - 1 \choose j} + {i - 1 \choose j - 1})\)

\(= -2\sum_{i = j}^{n - 1} (-2)^i{i \choose j} -2\sum_{i = j - 1}^{n - 1} (-2)^i{i \choose j - 1}\)

\(= -2a_j + 2(-2)^{n} {n \choose j} - 2a_{j - 1} + 2(-2)^{n} {n \choose j - 1}\)

轉換為遞推式\(a_j = \frac{1}{3} (2(-2)^n {n \choose j} + 2(-2)^n{n \choose j - 1} - 2a_{j - 1})\)

欲求的答案就是\(\sum_{j = 0}^{n} (-1)^ja_j \sum_{i = 0}^{n} i![x^i]e^{jx}\)

我們發現答案就是\(\sum_{i = 0}^{n} i![x^i]e^{jx} = \sum_{i = 0}^{n} j^i\),可以使用等比數列求和公式計算。

我們需要計算\(j^{n + 1}\),這可以先計算出\(j\)為素數處的取值,然后再用線性篩算出\(1 \leq j \leq n\)時的取值。復雜度變成了\(O(\frac{n}{\ln n} \cdot log_2{n}) = O(n)\)

於是,我們在\(O(n)\)的時間內做出了本題。順便獲得目前的rk1.

代碼如下:

#include <bits/stdc++.h>
#define debug(x) cerr << #x << " " << (x) << endl
using namespace std;

const int N = 100005;
const long long mod = 998244353ll;

int n, pri[N], cnt = 0;
bool is_pri[N];
long long pw1[N], pw2[N], inv[N], binom[N], a[N], ans = 0ll;

long long qpow (long long a, long long b) {
	long long res = 1ll;
	for (; b; b >>= 1, a = a * a % mod) {
		if (b & 1) res = res * a % mod;
	}
	return res;
}

void init () {
	pw1[1] = pw2[0] = inv[1] = 1ll;
	for (int i = 1; i <= max(n, 3); i++) is_pri[i] = (i != 1), pw2[i] = 2ll * (mod - pw2[i - 1]) % mod;
	for (int i = 2; i <= max(n, 3); i++) {
		inv[i] = (mod / i) * (mod - inv[mod % i]) % mod;
		if (is_pri[i]) pw1[i] = qpow(i, n + 1), pri[cnt++] = i;
		for (int j = 0; j < cnt && i * pri[j] <= n; j++) {
			is_pri[i * pri[j]] = false;
			pw1[i * pri[j]] = pw1[i] * pw1[pri[j]] % mod;
			if (i % pri[j] == 0) break;
		}
	}
	binom[0] = 1ll;
	for (int i = 1; i <= n; i++) binom[i] = binom[i - 1] * (n - i + 1) % mod * inv[i] % mod;
}

int main () {
	scanf("%d", &n), init();

	a[0] = 0ll;
	for (int i = 0; i <= n; i++) a[0] = (a[0] + pw2[i]) % mod;
	for (int i = 1; i <= n; i++) {
		a[i] = pw2[n] * (binom[i] + binom[i - 1]) % mod;
		a[i] = (a[i] - a[i - 1] + mod) % mod;
		a[i] = 2ll * a[i] % mod * inv[3] % mod;
	}

	for (int i = 0; i <= n; i++) {
		if (!i) ans = (ans + a[i]) % mod;
		else if (i == 1) ans = (ans + mod * mod - a[i] * (n + 1)) % mod;
		else if (i & 1) ans = (ans + mod * mod - a[i] * (pw1[i] + mod - 1) % mod * inv[i - 1]) % mod;
		else ans = (ans + a[i] * (pw1[i] + mod - 1) % mod * inv[i - 1]) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM