辛勤二更
- 题目
- 题解
- 错排数概念
- 错排数递推公式及其证明
- 代码实现
这种题做的时候:
做完后:正常这就是生活,我们要学会习惯
题目
求有多少种长度为 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个数的错排数:DP[n]=(n−1)∗(DP[n−1]+DP[n−2])DP[n]=(n-1)*(DP[n-1]+DP[n-2])DP[n]=(n−1)∗(DP[n−1]+DP[n−2])
证明如下:
①考虑第n个元素,把它放在某一个位置,比如位置k,一共有n-1种放法
②考虑第k个元素,这时有两种情况:
(1)把它放到位置n,那么对于除n以外的n-1个元素,由于第k个元素放到了位置n,所以剩下n-2个元素的错排即可,有f(n−2)f(n-2)f(n−2)种放法;
(2)第k个元素不放到位置n,这时对于这n-1个元素的错排,有f(n−1)f(n-1)f(n−1)种放法
运用加法以及乘法原理,得证完毕。。。
处理完n-m后,我们就要处理m,要知道选的m的位置不同算不同的方案哦(⊙o⊙)!
这个其实就是排列组合,从n个数中选m个的方案数:
Cnm=n!m!∗(n−m)!C_n^m=\frac{n!}{m!*(n-m)!}Cnm=m!∗(n−m)!n!
这里写出来后就发现这里面涉及到了除法,而取模运算中是不能进行除法运算的
所以我们就要去求m!m!m!和(n−m)!(n-m)!(n−m)!各自的逆元与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~~~~~~