SDOI 2016 排列计数

博客介绍了SDOI 2016竞赛中的排列计数问题,涉及全错位排列数的概念及其递推公式。通过容斥原理给出通项公式,并提供代码实现,用于快速计算特定条件下的排列数。

SDOI 2016 排列计数

题目链接

设满足题目条件且长度为n恰好有m个数稳定的序列数为 a n s ( n , m ) ans(n,m) ans(n,m)
那么易得题目答案ANS可表示为:
A N S = a n s ( n , m ) = C ( n , m ) ∗ a n s ( n − m , 0 ) ANS=ans(n,m)=C(n,m)*ans(n-m,0) ANS=ans(n,m)=C(n,m)ans(nm,0)
因为题目规定 n , m ≤ 1 e 6 n,m\le1e6 n,m1e6,所以C(n,m)可以通过预处理阶乘+乘法逆元快速求得。
至于ans(n-m,0),又被称作为全错位排位数。
即n个相异的元素排成一排且 a i ≠ i a_i\ne i ai=i的排列个数。
设长度为n的全错位排列数为Fn,有如下递推公式:
F n = ( n − 1 ) ( F n − 1 + F n − 2 ) , n ≥ 3 F_n=(n-1)(F_{n-1}+F_{n-2}),n\ge3 Fn=(n1)(Fn1+Fn2),n3,其中 F 1 = 0 , F 2 = 1 F_1=0,F_2=1 F1=0,F2=1。另外对于长度为0的序列,规定其全错位排列数为1。

证明:依据递推的思想,显然 F 1 = 0 , F 2 = 1 F_1=0,F_2=1 F1=0,F2=1,对于 n ≥ 3 n\ge3 n3,不妨设第n个数排在了第 k ( k ≠ n ) k(k\ne n) k(k=n)位。
1、若第k个数排在了第n位,那么对于n和k两个位置满足 a n ≠ n , a k = n ≠ k a_n\ne n,a_k=n\ne k an=n,ak=n=k,那么只剩下了n-2个数,显然剩下n-2个数可构成的全错位排列数为 F n − 2 F_{n-2} Fn2
2、若第k个数没有排在第n位,则规定了 a n ≠ k a_n\ne k an=k,那么可以将第n位假想为第k位,那么问题就转换成了n-1个数的全错位排列问题,解为 F n − 1 F_{n-1} Fn1
由于 1 ≤ k < n 1\le k< n 1k<n,所以 F n = ( n − 1 ) ( F n − 1 + F n − 2 ) F_n=(n-1)(F_{n-1}+F_{n-2}) Fn=(n1)(Fn1+Fn2)

接下来利用容斥原理来推出 F n F_n Fn的通项公式:

设n个数组成的全排列集合为I,则 ∣ I ∣ = n ! |I|=n! I=n!。设满足 a i = i a_i=i ai=i的排列的集合为 A i ( 1 ≤ i ≤ n ) A_i(1\le i \le n) Ai(1in)。有 ∣ A i ∣ = ( n − 1 ) ! |A_i|=(n-1)! Ai=(n1)! ∣ A i ∩ A j ∣ = ( n − 2 ) ! ( 1 ≤ i < j ≤ n ) |A_i\cap A_j|=(n-2)!(1\le i<j\le n) AiAj=(n2)!(1i<jn) ∣ A i ∩ A i + 1 ∩ . . ∩ A i + k ∣ = ( n − k − 1 ) ! |A_i\cap A_{i+1}\cap..\cap A_{i+k}|=(n-k-1)! AiAi+1..Ai+k=(nk1)!

F n = ∣ I ∣ − ∣ A 1 ∪ A 2 ∪ . . . ∪ A n ∣ = ∣ I ∣ − ( C n 1 ∣ A 1 ∣ − C n 2 ∣ A 1 ∩ A 2 ∣ + C n 3 ∣ A 1 ∩ A 2 ∩ A 3 ∣ + . . + ( − 1 ) n − 1 ∣ A 1 ∩ A 2 ∩ . . ∩ A n ∣ ) = n ! − C n 1 ( n − 1 ) ! + C n 2 ( n − 2 ) ! + . . . + ( − 1 ) i C n i ( n − i ) ! + . . + ( − 1 ) n C n n 0 ! = n ! ∑ i = 0 n ( − 1 ) i 1 i ! \begin{aligned}F_n&=|I|-|A_1\cup A_2\cup...\cup A_n|\\ &=|I|-(C_n^1|A_1|-C_n^2|A_1\cap A_2|+C_n^3|A_1\cap A_2\cap A_3|+..+(-1)^{n-1}|A_1\cap A_2\cap ..\cap A_n|)\\&=n!-C_n^1(n-1)!+C_n^2(n-2)!+...+(-1)^{i}C_n^i(n-i)!+..+(-1)^{n}C_n^n0!\\&=n!\sum_{i=0}^{n}(-1)^{i}\frac{1}{i!}\end{aligned} Fn=IA1A2...An=I(Cn1A1Cn2A1A2+Cn3A1A2A3+..+(1)n1A1A2..An)=n!Cn1(n1)!+Cn2(n2)!+...+(1)iCni(ni)!+..+(1)nCnn0!=n!i=0n(1)ii!1

从而也可以验证递推公式:
F n + 1 = n ⋅ ( F n + F n − 1 ) = n ⋅ ( n ! ∑ i = 0 n ( − 1 ) i 1 i ! + ( n − 1 ) ! ∑ i = 0 n − 1 ( − 1 ) i 1 i ! ) = n ⋅ ( n ! ∑ i = 0 n − 1 ( − 1 ) i 1 i ! + ( − 1 ) n + ( n − 1 ) ! ∑ i = 0 n − 1 ( − 1 ) i 1 i ! = ( n + 1 ) ! ∑ i = 0 n − 1 ( − 1 ) i 1 i ! + ( − 1 ) n ⋅ n = ( n + 1 ) ! ⋅ ( ∑ i = 0 n − 1 ( − 1 ) i 1 i ! + ( − 1 ) n ⋅ n ( n + 1 ) ! ) = ( n + 1 ) ! ⋅ ( ∑ i = 0 n − 1 ( − 1 ) i 1 i ! + ( − 1 ) n ⋅ n + 1 − 1 ( n + 1 ) ! ) = ( n + 1 ) ! ⋅ ( ∑ i = 0 n − 1 ( − 1 ) i 1 i ! + ( − 1 ) n ⋅ 1 n ! + ( − 1 ) n + 1 1 ( n + 1 ) ! ) = ( n + 1 ) ! ⋅ ∑ i = 0 n + 1 ( − 1 ) i 1 i ! = F n + 1 \begin{aligned}F_{n+1}&=n\cdot (F_n+F_{n-1})\\&=n\cdot (n!\sum_{i=0}^{n}(-1)^{i}\frac{1}{i!}+(n-1)!\sum_{i=0}^{n-1}(-1)^{i}\frac{1}{i!})\\&=n\cdot(n!\sum_{i=0}^{n-1}(-1)^{i}\frac{1}{i!}+(-1)^n+(n-1)!\sum_{i=0}^{n-1}(-1)^{i}\frac{1}{i!}\\&=(n+1)!\sum_{i=0}^{n-1}(-1)^{i}\frac{1}{i!}+(-1)^n\cdot n\\&=(n+1)!\cdot(\sum_{i=0}^{n-1}(-1)^{i}\frac{1}{i!}+(-1)^n\cdot\frac{n}{(n+1)!})\\&=(n+1)!\cdot(\sum_{i=0}^{n-1}(-1)^{i}\frac{1}{i!}+(-1)^n\cdot\frac{n+1-1}{(n+1)!})\\&=(n+1)!\cdot(\sum_{i=0}^{n-1}(-1)^{i}\frac{1}{i!}+(-1)^n\cdot\frac{1}{n!}+(-1)^{n+1}\frac{1}{(n+1)!})\\&=(n+1)!\cdot\sum_{i=0}^{n+1}(-1)^i\frac{1}{i!}\\&=F_{n+1}\end{aligned} Fn+1=n(Fn+Fn1)=n(n!i=0n(1)ii!1+(n1)!i=0n1(1)ii!1)=n(n!i=0n1(1)ii!1+(1)n+(n1)!i=0n1(1)ii!1=(n+1)!i=0n1(1)ii!1+(1)nn=(n+1)!(i=0n1(1)ii!1+(1)n(n+1)!n)=(n+1)!(i=0n1(1)ii!1+(1)n(n+1)!n+11)=(n+1)!(i=0n1(1)ii!1+(1)nn!1+(1)n+1(n+1)!1)=(n+1)!i=0n+1(1)ii!1=Fn+1

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int max_n=1e6+5;
const int mod=1e9+7;
ll factorial[max_n];
ll f[max_n];
ll rev[max_n];
void init(void)
{
	memset(rev,-1,sizeof(rev));
	factorial[0]=1;
	f[0]=1;
	f[1]=0;f[2]=1;
	for(ll i=1;i<max_n;i++)
	factorial[i]=factorial[i-1]*i%mod;
	for(ll i=3;i<max_n;i++)
	f[i]=((i-1)*(f[i-1]+f[i-2]))%mod;
}
ll qpow(ll index,ll n,ll p)
{
	if(rev[index]!=-1)return rev[index];
	ll a=factorial[index];
	ll ans=1;
	while(n)
	{
		if(n&1)ans=ans*a%p;
		a=a*a%p;
		n>>=1;
	}
	return rev[index]=ans;
}
ll C(ll n,ll m)
{
	if(m>n)return 0;
	return factorial[n]*qpow(m,mod-2,mod)%mod*qpow(n-m,mod-2,mod)%mod;
}
int main(void)
{
	int t;
	init();
	scanf("%d",&t);
	ll n,m;
	while(t--)
	{
		scanf("%lld%lld",&n,&m);
		printf("%lld\n",C(n,m)*f[n-m]%mod);
	}
}
//5
//1 0
//1 1
//5 2
//100 50
//10000 5000
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值