题目描述
求有多少种长度为 $n$ 的序列 $A$,满足以下条件:
- $1∼n$ 这 $n$ 个数在序列中各出现了一次。
- 若第 $i$ 个数 $A_i$ 的值为 $i$,则称 $i$ 是稳定的,序列恰好有 $m$ 个数是稳定的。
由于满足条件的序列可能很多,所以请你将序列数对 $10^9+7$ 取模后输出。
输入格式
第一行一个数 $T$,表示有 $T$ 组数据。
接下来 $T$ 行,每行两个整数 $n、m$。
输出格式
输出 $T$ 行,每行一个整数,表示求出的序列数对 $10^9+7$ 取模后的值。
数据范围
$T \leq 500000,n \leq 1000000,m \leq 1000000$
输入样例:
5
1 0
1 1
5 2
100 50
10000 5000
输出样例:
0
1
20
578028887
60695423
算法
非常经典的错排问题。
首先,选出 $m$ 个数当稳定的数,共有 $C_n^m$ 种选法。
剩下 $n-m$ 个数就是错排问题。设 $f_i$ 表示 $i$ 个数的错排方案数,那么,设 $i$ 放在了第 $j$ 个位置,那对于 $j$,显然有 $i-1$ 种选择。我们可以分两种情况:
- 把 $j$ 放在第 $i$ 个位置,这样就形成了 $i-2$ 个数字的错排。
- 把 $j$ 前 $i-1$ 个位置,这样就形成了 $i-1$ 个数字的错排。
综上所述,我们可以得到递推式 $f_i=(i-1) \times (f_{i-1}+f_{i-2})$,最后 $f_{n-m} \times C_n^m$ 就是答案。
时间复杂度
$O(nlog_n)$
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7, N = 1000010;
ll t, n, m, fac[N], inv[N], f[N];
ll qmi(ll a, ll b, ll p) {
ll ans = 1;
for (; b; b >>= 1) {
if (b & 1) {
ans = ans * a % p;
}
a = a * a % p;
}
return ans;
}
ll C(ll a, ll b) {
return fac[a] * inv[b] % mod * inv[a-b] % mod;
}
void init() {
f[0] = 1, f[1] = 0, f[2] = 1, fac[0] = 1, fac[1] = 1, fac[2] = 2, inv[0] = 1, inv[1] = 1, inv[2] = qmi(2, mod - 2, mod);
for (int i = 3; i < N; ++i) {
f[i] = (i - 1) * (f[i-1] + f[i-2]) % mod;
fac[i] = fac[i-1] * i % mod;
inv[i] = qmi(fac[i], mod - 2, mod);
}
}
int main() {
init();
scanf("%lld", &t);
while (t--) {
scanf("%lld%lld", &n, &m);
printf("%lld\n", n <= m ? 1 : C(n, m) * f[n-m] % mod);
}
return 0;
}