Description
有一个排列 $a_1, a_2,\cdots, a_n$。已知一种求最大值的方式:遍历一遍序列,如果一个数比他后面的 $k$ 个值都要大,则直接返回该值。求有多少个排列使得这种方式返回的答案是错误的。
$n, k \leq 10^6$ 。
Solution
考虑如何求正确的排列个数。可以发现,如果一个序列在求最大值时还没有遍历到 $n$ 就中途退出了,那么他返回的一定不会是正确的值。
于是可以设计出 $dp$ 状态:$dp_i$ 表示遍历完前 $i$ 个数依然没有返回的 $1,2,\cdots, i$ 的排列个数 。
注意,这里只考虑 $1, 2, \cdots, i$ 的排列个数,即他们的相对大小关系。
转移时,我们可以枚举前 $i - 1$ 个数的最大值所在的位置 $j$。显然有几个性质。一是 $j$ 要在 $[i - k + 1, i]$ 内,否则就会退出;二是在遍历到这个最大值之前不能退出;三是这个最大值之后的 $i - j$ 个位置可以随便交换位置,原因是他们不会对退出这件事造成任何影响。
于是,我们可以列出转移方程:
暴力 dp 是 $O(n^2)$ 的,考虑把组合数拆开,简单推导可得:
直接维护 $\sum\limits_{j=i-k}^{i-1} \frac{dp_j}{j!}$ 即可。
最后的答案如何计算?考虑最大值的位置 $i$ ,有:
时间复杂度 $O(n)$。
Code
代码中可以考虑直接维护 $\frac{dp_i}{i!}$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <bits/stdc++.h>
using namespace std;
const int N = (int)1e6 + 10; const int mod = 1000000007;
int n, k, f[N], g[N], dp[N];
int add(int x, int y) { return (x += y) >= mod ? x - mod : x; }
int fpw(int x, int k, int p) { int ret = 1; while(k) { if(k & 1) ret = 1ll * ret * x % p; x = 1ll * x * x % p; k >>= 1; } return ret; }
int main() { scanf("%d %d", &n, &k); f[0] = 1; for(int i = 1; i <= n; i++) f[i] = 1ll * f[i - 1] * i % mod; g[n] = fpw(f[n], mod - 2, mod); for(int i = n - 1; i >= 0; i--) g[i] = 1ll * g[i + 1] * (i + 1) % mod; int s = 0; dp[0] = 1; for(int i = 1; i <= n; i++) { s = add(s, dp[i - 1]); if(i > k) s = add(s, mod - dp[i - k - 1]); dp[i] = 1ll * g[i] * f[i - 1] % mod * s % mod; } s = 0; for(int i = 1; i <= n; i++) s = add(s, dp[i - 1]); printf("%lld\n", 1ll * f[n - 1] * add(n, mod - s) % mod); return 0; }
|