1. 程式人生 > 實用技巧 >[Luogu] P4071 [SDOI2016]排列計數

[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;
}