P4071 (递推+计数)
题目大意:
给定两个数 \(n , m\) ,求 \(n\) 的排列中恰好有 \(m\) 个数在对应位置的方案数
题目分析:
对于一个长度为 \(n\) 的排列,我们可以钦定哪几位是在对应位置上,那么问题就变成了在 \(n\) 个数里面选出 \(m\) 个数的方案数 \(C^m_n\),显而易见对于这 \(C^m_n\) 种方案数都是不同的。
那么我们考虑对于其他 \(n - m\) 为错位的情况,我们设 \(F_n\) 为长度为 \(n\) 的全错排序列的方案数 ,那么一定有一个数 \(k\) 放在了 \(n\) 的位置上 , 所以我们考虑对于数 \(n\) 是否放在了某个位置 \(k\) , 我们分为两种情况 :
- [\(1\)]:数 \(n\) 放在了 \(k\) 的位置上 那么 \(n , k\) 的位置确定了,可知 \(F_n += F_{n - 2}\)
- [\(2\)]:数 \(n\) 未放在 \(k\) 的位置上,那么 \(n\) 一定不能放在 \(k\) 的位置,可知 \(F_n += F_{n - 1}\)
仔细思考一下,对于 \(F_{n - 1}\) 与 \(F_{n - 2}\) 是否会出现重复的情况,答案是不会的。因为对于第二种情况种剔除了 \(n\) 在次放在 \(k\) 位置上的情况。
根据乘法原理可知 \(F\) 的递推式 : \(F_i = (i - 1) * (F_{i - 1} + F_{i - 2})\)
显而易见最后答案为: \(ans = C^m_n * F_{n - m}\)
代码实现:
我们可以先预处理出 \(1 - N\) 的所有阶乘与它对应的逆元。
注意 \(0! = 1\)
对于 \(F\) 数组递推求解即可
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int M = 1e6 + 7;
int jc[M] , inv[M] , f[M];
int Pow(int a , int b) {
int ans = 1;
while(b) {
if(b & 1) ans = ans * a % mod;
a =a * a % mod;
b >>= 1;
}
return ans;
}
signed main () {
ios::sync_with_stdio(0),cin.tie(0);
int t; cin >> t;
jc[0] = 1 , jc[1] = 1;
inv[0] = 1;
for(int i = 1; i <= 1e6; ++ i) {
jc[i] = jc[i - 1] * i % mod;
inv[i] = Pow(jc[i] , mod - 2);
}
f[0] = 1 ,f[1] = 0 , f[2] = 1;
for(int i = 3; i <= 1e6; ++ i) f[i] = (i - 1) * (f[i - 1] + f[i - 2]) % mod;
while(t --) {
int n , m; cin >> n >> m;
cout << (((jc[n] * inv[m] % mod)* inv[n - m] % mod) * f[n - m] % mod) << '\n';
}
return 0;
}