[SDOI2016]排列计数 (错排数概念 + 递推公式【附带证明】)

这种题做的时候:在这里插入图片描述
做完后:在这里插入图片描述正常这就是生活,我们要学会习惯

题目

求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的
满足条件的序列可能很多,序列数对 10^9+7取模。

输入格式
第一行一个数 T,表示有 T 组数据。
接下来 T 行,每行两个整数 n、m。
输出格式
输出 T 行,每行一个数,表示求出的序列数

输入输出样例
输入
5
1 0
1 1
5 2
100 50
10000 5000
输出
0
1
20
578028887
60695423
说明/提示
测试点 1 ~ 3:T=1000, n≤8, m≤8;
测试点 4 ~ 6: T=1000, n≤12,m≤12;
测试点 7 ~ 9: T=1000, n≤100, m≤100;
测试点 10 ~ 12: T = 1000,n≤1000, m≤1000;
测试点 13 ~ 14: T = 500000, n≤1000, m≤1000;
测试点 15 ~ 20: T = 500000, n≤1000000,m≤1000000。

题解

突然想感慨一番:在这里插入图片描述

步入正轨↓(我真的很讨厌这种有模数的方案数题)

题意很简单,n个数固定m个数,那么就有n-m个数上的位置是不稳定的
也就是这n-m个数要满足A[i]≠i,对于这n-m中的某个位置i,就只有n-m-1种填法


错排数概念

这里就引入错排数的概念

n个有序的元素应有n!个不同的排列,如若一个排列使得所有的元素不在原来的位置上,则称这个排列为错排;有的也称之为重排

错排数递推公式及其证明

求n个数的错排数: D P [ n ] = ( n − 1 ) ∗ ( D P [ n − 1 ] + D P [ n − 2 ] ) DP[n]=(n-1)*(DP[n-1]+DP[n-2]) DP[n]=(n1)(DP[n1]+DP[n2])
证明如下
①考虑第n个元素,把它放在某一个位置,比如位置k,一共有n-1种放法
②考虑第k个元素,这时有两种情况:
(1)把它放到位置n,那么对于除n以外的n-1个元素,由于第k个元素放到了位置n,所以剩下n-2个元素的错排即可,有 f ( n − 2 ) f(n-2) f(n2)种放法;
(2)第k个元素不放到位置n,这时对于这n-1个元素的错排,有 f ( n − 1 ) f(n-1) f(n1)种放法
运用加法以及乘法原理,得证完毕。。。


处理完n-m后,我们就要处理m,要知道选的m的位置不同算不同的方案哦(⊙o⊙)!
这个其实就是排列组合,从n个数中选m个的方案数:
C n m = n ! m ! ∗ ( n − m ) ! C_n^m=\frac{n!}{m!*(n-m)!} Cnm=m!(nm)!n!

这里写出来后就发现这里面涉及到了除法,而取模运算中是不能进行除法运算的
所以我们就要去求 m ! m! m! ( n − m ) ! (n-m)! (nm)!各自的逆元与 n ! n! n!相乘,
有很多方法都可以完成,费马小定理,扩展欧几里得…


在这里插入图片描述

代码实现

#include <cstdio>
#define mod 1000000007
#define LL long long
#define MAXN 1000000
int T, n, m;
LL sum[MAXN + 5], inv[MAXN + 5], dp[MAXN + 5];
LL qkpow ( LL x, int y ) {
	LL ans = 1;
	while ( y ) {
		if ( y & 1 )
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans % mod;
}
LL getinv ( LL x ) {
	return qkpow ( x, mod - 2 );
}
int main() {
	scanf ( "%d", &T );
	sum[0] = 1;inv[0] = 1;
	for ( int i = 1;i <= MAXN;i ++ ) {
		sum[i] = sum[i - 1] * i % mod;
		inv[i] = getinv ( sum[i] );
	}
	dp[0] = dp[2] = 1;
	for ( int i = 3;i <= MAXN;i ++ )
		dp[i] = ( dp[i - 1] + dp[i - 2] ) % mod * ( i - 1 ) % mod;
	while ( T -- ) {
		scanf ( "%d %d", &n, &m );
		printf ( "%lld\n", sum[n] * inv[n - m] % mod * inv[m] % mod * dp[n - m] % mod ); 
	}
	return 0;
} 

byebye~~~~~~
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值