设(f(n, k))表示填(n)个格子,填了数字有(k)个的方案数,有初值(f(0, 0) = 1)。
不填第(n)个格子的方案数为(f(n - 1, k)),填第(n)个格子时,相当于从(n - (k - 1))个没用过的数中选一个出来填进去,方案数为((n - k + 1) * f(n - 1, k - 1))。
发现这个东西和第二类斯特林数的递推式很相似,打个表可以发现(f(n - 1, k) = S(n, n - k)),套上第二类斯特林数的公式,时间复杂度(O(nlogn))。
简单证明:
[f(n - 1, k) = f(n - 2, k) + (n - k) * f(n - 2, k - 1)
]
令(m = n - k),
[f(n - 1, n - m) = f(n - 2, n - m) + m * f(n - 2, n - m - 1)
]
再令(f(n - 2, n - m - 1) = g(n, m)),
[g(n + 1, m) = g(n, m - 1) + m * g(n, m)
]
(这个地方有一些难懂,事实上(f)的两个参数都加1,(g)中的第二个参数是由(f)中的两个参数作差得到的,所以应当不变)
(g)就是第二类斯特林数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef pair <int, int> pin;
const int N = 1e6 + 5;
const int Maxn = 1e6;
const ll P = 998244353LL;
ll fac[N], ifac[N];
template <typename T>
inline void read(T &X) {
char ch = 0; T op = 1;
for (X = 0; ch > '9' || ch < '0'; ch = getchar())
if (ch == '-') op = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar())
X = (X * 10) + ch - '0';
X *= op;
}
inline ll fpow(ll x, ll y) {
ll res = 1;
for (; y > 0; y >>= 1) {
if (y & 1) res = res * x % P;
x = x * x % P;
}
return res;
}
inline ll getc(ll n, ll m) {
if (n < m) return 0LL;
return fac[n] * ifac[m] % P * ifac[n - m] % P;
}
inline void inc(ll &x, ll y) {
x += y;
if (x >= P) x -= P;
}
inline void sub(ll &x, ll y) {
x -= y;
if (x < 0) x += P;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("sample.in", "r", stdin);
clock_t st_clock = clock();
#endif
fac[0] = 1;
for (int i = 1; i <= Maxn; i++) fac[i] = fac[i - 1] * i % P;
ifac[Maxn] = fpow(fac[Maxn], P - 2);
for (int i = Maxn - 1; i >= 0; i--) ifac[i] = ifac[i + 1] * (i + 1) % P;
ll n, m, k, res = 0;
read(n), read(k);
m = n - k;
for (int i = 0; i <= m; i++) {
if (i & 1) sub(res, getc(m, i) * fpow(m - i, n) % P);
else inc(res, getc(m, i) * fpow(m - i, n) % P);
}
res = res * ifac[m] % P;
printf("%lld
", res);
#ifndef ONLINE_JUDGE
clock_t ed_clock = clock();
printf("time = %f ms
", (double)(ed_clock - st_clock) / CLOCKS_PER_SEC * 1000);
#endif
return 0;
}