提供一种好想的优化方法。先拿出柿子(这里的 dfs(n, k)
相当于题解的 \({n \choose k} - z(n, k)\)):
int dfs(int n, int k) {
if (n == k) return 0;
if (n == 1) return 0;
if (k == 1) return 0;
if (~f[n][k]) return f[n][k];
return f[n][k] = (dfs(n - 1, k) + dfs(n - 1, k - 1) + (n - k + 1 <= k)) % mod;
}
每次答案是 C(n, k) - dfs(n, k)
。考虑一个 \((i, j)\) 数对对答案的贡献,只有在 \(i \neq 1, j \neq 1, i \neq j, i - j + 1 \leq j\) 时,它会贡献 \(1\),并依次往上记录到答案中。这个东西和组合数的递推方式非常像,此时 dfs(n, k)
计算中就会包含 \(n - i \choose k - j\) 个 \((i, j)\) 数对,这就是它的贡献。
for (int i = 2; i <= n; i++)
for (int j = max(k - n + i, i + 2 >> 1); j <= min(i - 1, k); j++)
(ans += C(n - i, k - j)) %= mod; // k - n + i & k 的限制是为了 n - i >= k - j >= 0
然后你可以得到这样的东西来计算 dfs(n, k)
。然后我们固定一下 \(j\),能贡献它的 \(i\) 是一段区间,可以使用变上限求和 \(\sum\limits_{i = 0} ^ n {i \choose k} = {n + 1 \choose k + 1}\) 来快速计算。
namespace liuzimingc {
#define pii pair<int, int>
#define endl '\n'
const int maxn = 1e7 + 5, mod = 998244353;
int T, n, k;
long long fac[maxn], inv[maxn];
int qkpow(long long a, int b) {
long long ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int C(int n, int m) {
if (n < m) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int get(int n, int k) {
return C(n + 1, k + 1);
}
int get_sum(int l, int r, int k) {
return (get(r, k) - get(l - 1, k) + mod) % mod;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
fac[0] = 1;
for (int i = 1; i <= 10000001; i++) fac[i] = fac[i - 1] * i % mod;
inv[10000001] = qkpow(fac[10000001], mod - 2);
for (int i = 10000000; ~i; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
cin >> T;
while (T--) {
cin >> n >> k;
int ans = 0;
for (int x = 0; x <= k; x++) {
int j = k - x;
int l = n - min(j + n - k, 2 * j - 1), r = n - (j + 1);
if (l <= r) (ans += get_sum(l, r, x)) %= mod;
}
cout << (C(n, k) - ans + mod) % mod << endl;
}
return 0;
}
#undef int
}