[Luogu] P4071 [SDOI2016]排列计数
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;
}