2020ICPC上海 E. The Journey of Geor Autumn
题意
有多少个长度为 n n n 的排列满足 i > k , i ≤ n , a i > m i n ( a i − k , … , a i − 1 ) i>k,i\le n,a_i>min(a_{i-k},\dots,a_{i-1}) i>k,i≤n,ai>min(ai−k,…,ai−1) 。
解法
首先最小的数一定在前 k k k 位,假设最小的数在第 x x x 位,那么这个排列就被分成了 [ 1 , x − 1 ] , [ x + 1 , n ] [1,x-1],[x+1,n] [1,x−1],[x+1,n] 两部分,前 x − 1 x-1 x−1 位可以任意放,并且要求 [ x + 1 , n ] [x+1,n] [x+1,n] 也能组成一个合法排列。
设 d p [ i ] dp[i] dp[i] 表示长度为 i i i 的合法序列i数。
那么可以得到递推式
d
p
[
i
]
=
∑
j
=
1
m
i
n
(
k
,
i
)
d
p
[
i
−
j
]
⋅
A
i
−
1
j
−
1
=
∑
j
=
1
m
i
n
(
k
,
i
)
d
p
[
i
−
j
]
⋅
(
i
−
1
)
!
(
i
−
j
)
!
=
(
i
−
1
)
!
∑
j
=
1
m
i
n
(
k
,
i
)
d
p
[
i
−
j
]
(
i
−
j
)
!
令
前
缀
和
p
r
e
[
i
]
=
∑
j
=
0
i
d
p
[
j
]
j
!
\begin{aligned} dp[i] & = \sum_{j=1}^{min(k,i)} dp[i-j]\cdot A_{i-1}^{j-1}\\ & = \sum_{j=1}^{min(k,i)} dp[i-j]\cdot \frac{(i-1)!}{(i-j)!}\\ & = (i-1)!\sum_{j=1}^{min(k,i)} \frac{dp[i-j]}{(i-j)!} \end{aligned}\\ 令前缀和\space pre[i]=\sum_{j=0}^{i}\frac{dp[j]}{j!}
dp[i]=j=1∑min(k,i)dp[i−j]⋅Ai−1j−1=j=1∑min(k,i)dp[i−j]⋅(i−j)!(i−1)!=(i−1)!j=1∑min(k,i)(i−j)!dp[i−j]令前缀和 pre[i]=j=0∑ij!dp[j]
然后可以通过预处理前缀和来得到答案。
注意需要预处理阶乘的逆元。
代码
#pragma region
//#pragma optimize("Ofast")
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
#define tr t[root]
#define lson t[root << 1]
#define rson t[root << 1 | 1]
#define rep(i, a, n) for (int i = a; i <= n; ++i)
#define per(i, a, n) for (int i = n; i >= a; --i)
#pragma endregion
const int maxn = 1e7 + 5;
const ll mod = 998244353;
ll dp[maxn], pre[maxn];
ll powmod(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll fac[maxn], inv[maxn];
void init() {
fac[1] = fac[0] = 1;
for (int i = 2; i < maxn; ++i) fac[i] = fac[i - 1] * i % mod;
inv[maxn - 1] = powmod(fac[maxn - 1], mod - 2);
for (int i = maxn - 2; i >= 0; --i)
inv[i] = inv[i + 1] * (i + 1) % mod;
}
int main() {
ll n, k;
scanf("%lld%lld", &n, &k);
init();
dp[1] = 1, pre[1] = 2, pre[0] = 1;
ll fac = 1;
for (int i = 2; i <= n; ++i) {
fac = fac * (i - 1) % mod;
if (i <= k)
dp[i] = fac * pre[i - 1] % mod;
else
dp[i] = fac * (pre[i - 1] - pre[i - k - 1] + mod) % mod;
pre[i] = pre[i - 1] + dp[i] * inv[i] % mod;
if (pre[i] > mod) pre[i] -= mod;
}
printf("%lld\n", dp[n]);
}
/**
* dp[n] 表示答案
* dp[i]
* 最小值在x位置
* dp[n] += dp[i] * A(n-1,i-1)
**/