[Luogu] P4071 [SDOI2016]排列计数

(Link)

Description

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

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

Solution

算是错排的板子题了。

错排,就是对于某个(1sim{n})的排列,满足(forall{iin[1,n]},a_i e{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)=sumlimits_{i=0}^n(-1)^idbinom{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
", (((pw[n] * inv[m]) % mod) * inv[n - m] % mod) * wp[n - m] % mod);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/andysj/p/13966159.html