BZOJ 4517: [Sdoi2016]排列计数

Description

有T个询问,每个询问给出n、m,求1-n所有的排列中,恰好有m个位置满足P_i=i的排列个数。

( T<=500000, n<=1000000, m<=1000000. )

Solution

Key:错排,逆元。

错排就是指将1-n这n个数放入标号为1-n的袋子中,全部装错的情况种数。

设\(F_n\)表示这个东西,那么考虑将n放到k的位置上,如果k放到了n的位置,那么剩下n-2个元素,就是\(F_{n-2}\),如果k不放到n的位置上,那么剩下n-1个元素,就是\(F_{n-1}\),根据加法原理总结最后的公式:

\[F_n=(n-1)*(F_{n-1}+F_{n-2})\]

数a的逆元就是指在模p意义下满足\(a*x\equiv 1 \pmod {p}\)的最小的x。

设m的逆元为k,那么求n/m在模p意义下的值时就可以求n*k。

然后说这道题,我们先从n个元素中挑出m个来让他们的P_i=i,然后将n-m个错排即可。答案为:\((_m^n)*F_{n-m}\)。

Code

 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
41
42
43
44
45
46
47
48
49
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long LL;
const int INF = 1<<30;
int getint() {
	int r = 0, k = 1; char c = getchar();
	for (; '0' > c || c > '9'; c = getchar()) if (c == '-') k = -1;
	for (; '0' <= c && c <= '9'; c = getchar()) r = r * 10 - '0' + c;
	return r * k;
}
LL F[1000005], Frac[1000005], Fran[1000005];
const LL mod = 1000000007;
const LL moe = 1000000005;
int n, m;
LL ksm(LL a, LL b) {
	LL ret = 1, tmp = a;
	while (b) {
		if (b & 1) ret = (ret * tmp) % mod;
		tmp = (tmp * tmp) % mod;
		b >>= 1;
	}
	return ret;
}
LL C(int n, int m) {
	return Frac[n] * Fran[m] % mod * Fran[n-m] % mod;
}
void init() {
	F[0] = 1; F[1] = 0; F[2] = 1; Frac[0] = 1; Fran[0] = 1;
	for (int i = 1; i <= 1000000; ++i) Frac[i] = Frac[i-1] * i % mod;
	for (int i = 1; i <= 1000000; ++i) Fran[i] = ksm(Frac[i], moe);
	for (int i = 3; i <= 1000000; ++i) F[i] = (F[i-1] + F[i-2]) % mod * (i-1) % mod;
}
int main() {
	init();
	int T = getint();
	while (T--) {
		n = getint(); m = getint();
		if (m > n) {
			printf("0\n");
		} else {
			LL ans = (C(n, m) * F[n-m]) % mod;
			printf("%lld\n", ans);
		}
	}
	return 0;
}