[Luogu] P4071 [SDOI2016]排列计数

\(Link\)

Description

多组询问,求有多少\(1\)\(n\)的排列\(a\),满足序列恰好有\(m\)个位置\(i\),使得\(a_i = i\)

答案对\(10^9 + 7\)取模。

Solution

算是错排的板子题了。

错排,就是对于某个\(1\sim{n}\)的排列,满足\(\forall{i\in[1,n]},a_i\ne{i}\)

那么这道题其实就是求\(\dbinom{n}{m}D(n-m)\)

现在要解决如何求\(D(n)\),有两种方法求:

首先是递推:对于一个错排\(D(n)\),不失一般性,考虑\(1\)这个数放在哪个位置。它肯定不能放在\(a_1\),剩下的位置随便放,所以先乘上一个\(n-1\)。然后再考虑\(2\)放在哪里:如果\(2\)放在\(a_1\),那么就是一个子问题\(D(n-2)\);如果\(2\)不能放在\(a_1\),那\(a_1\)其实就相当于\(a_2\),这时是一个子问题\(D(n-1)\)。综上,\(D(n)=(n-1)(D(n-1)+D(n-2))\),特别地,\(D(0)=1,D(1)=0\)

其次是容斥: 正整数\(1, 2, 3, …, n\)的全排列有 \(n!\) 种,其中第\(k\)位是\(k\)的排列有\((n-1)!\)种;当\(k\)分别取\(1, 2, 3, …, n\)时,共有\(\dbinom{n}{1}*(n-1)!\)种排列是至少放对了一个的,由于所求的是错排的种数,所以应当减去这些排列;但是此时把同时有两个数不错排的排列多排除了一次,应补上;在补上时,把同时有三个数不错排的排列多补上了一次,应排除......继续这一过程,得到错排的排列种数为

\(D(n)=\sum\limits_{i=0}^n(-1)^i\dbinom{n}{i}(n-i)!\)

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

const ll mod = 1e9 + 7;

ll t, n, m, pw[1000005], inv[1000005], wp[1000005];

ll read()
{
	ll x = 0; char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x;
}

ll qpow(ll base, ll p)
{
	ll res = 1;
	while (p)
	{
		if (p & 1) res = res * base % mod;
		base = base * base % mod;
		p >>= 1;
	}
	return res;
}

int main()
{
	t = read();
	wp[0] = 1; wp[1] = 0; wp[2] = 1;
	for (ll i = 3; i <= 1000000; i ++ )
		wp[i] = (i - 1) * ((wp[i - 1] % mod) + (wp[i - 2] % mod)) % mod;
	pw[1] = 1;
	for (ll i = 2; i <= 1000001; i ++ )
		pw[i] = pw[i - 1] * i % mod;
	inv[1000001] = qpow(pw[1000001], mod - 2);
	for (ll i = 1000000; i >= 0; i -- )
		inv[i] = inv[i + 1] * (i + 1) % mod;
	while (t -- )
	{
		n = read(); m = read();
		printf("%lld\n", (((pw[n] * inv[m]) % mod) * inv[n - m] % mod) * wp[n - m] % mod);
	}
	return 0;
}
posted @ 2020-11-12 20:44  andysj  阅读(77)  评论(0编辑  收藏  举报