题目描述
给定初始序列 1,2,3,…,N1, 2, 3, \dots, N1,2,3,…,N,求有多少个排列满足:前 MMM 个位置中,恰好有 KKK 个数在它们原来的位置上(即排列后这些位置上的数字与初始序列相同)。
输入格式
- 第一行一个整数 TTT(T≤1000T \leq 1000T≤1000),表示测试用例数量。
- 接下来 TTT 行,每行三个整数 N,M,KN, M, KN,M,K(1≤N≤10001 \leq N \leq 10001≤N≤1000,M≤NM \leq NM≤N,K≤MK \leq MK≤M)。
输出格式
- 对每个测试用例,输出
Case X: answer,其中answer对 100000000710000000071000000007 取模。
题目分析
我们有一个长度为 NNN 的初始序列 1,2,…,N1, 2, \dots, N1,2,…,N。
我们要计算排列的数量,使得在前 MMM 个位置中,恰好有 KKK 个位置上的数字与初始序列相同,其余位置可以不同。
解题思路
这个问题可以拆分成两个部分:
-
选择固定的位置
在前 MMM 个位置中,选择 KKK 个位置,这些位置上的数字必须与初始序列相同。
选择方式数为组合数 (MK)\binom{M}{K}(KM)。 -
安排剩余的数字
剩下的 N−KN-KN−K 个位置(包括前 MMM 个位置中没被选中的 M−KM-KM−K 个位置,以及后 N−MN-MN−M 个位置)必须满足:- 前 MMM 个位置中,除了那 KKK 个固定位置外,剩下的 M−KM-KM−K 个位置上的数字不能是它们原来的数字(即错排条件)。
- 后 N−MN-MN−M 个位置没有限制,可以任意排列,但要考虑整体排列的合法性。
部分错排的容斥原理
设 R=M−KR = M - KR=M−K(前 MMM 个位置中需要错排的数量)。
我们考虑在 N−KN-KN−K 个位置上,前 RRR 个位置不能放它们原来的数字(这些数字来自初始序列的前 MMM 个位置中没被固定的那部分)。
定义 D(n,r)D(n, r)D(n,r) 表示 nnn 个元素的排列中,前 rrr 个位置是错排(即前 rrr 个位置上的元素都不是它们原来的元素),后 n−rn-rn−r 个位置任意。
用容斥原理计算:
D(n,r)=∑i=0r(−1)i(ri)(n−i)!
D(n, r) = \sum_{i=0}^{r} (-1)^i \binom{r}{i} (n-i)!
D(n,r)=i=0∑r(−1)i(ir)(n−i)!
解释:
- 选择 iii 个“坏事件”(前 rrr 个位置中恰好有 iii 个在原来的位置上),剩下的 n−in-in−i 个位置任意排列。
- 容斥原理求和即可。
最终公式
对于给定的 N,M,KN, M, KN,M,K:
Answer=(MK)×D(N−K,M−K)
\texttt{Answer} = \binom{M}{K} \times D(N-K, M-K)
Answer=(KM)×D(N−K,M−K)
其中
D(n,r)=∑i=0r(−1)i(ri)(n−i)!
D(n, r) = \sum_{i=0}^{r} (-1)^i \binom{r}{i} (n-i)!
D(n,r)=i=0∑r(−1)i(ir)(n−i)!
模 109+710^9+7109+7 计算。
算法步骤
- 预计算阶乘 fact[i]fact[i]fact[i] 和逆元阶乘 invFact[i]invFact[i]invFact[i] 到 Nmax=1000N_{\max} = 1000Nmax=1000。
- 对每个测试用例:
- 如果 K>MK > MK>M,答案是 000。
- 否则计算:
(MK)×∑i=0M−K(−1)i(M−Ki)×(N−K−i)! \binom{M}{K} \times \sum_{i=0}^{M-K} (-1)^i \binom{M-K}{i} \times (N-K-i)! (KM)×i=0∑M−K(−1)i(iM−K)×(N−K−i)!
模 109+710^9+7109+7。
- 输出结果。
复杂度分析
- 预计算阶乘和逆元:O(Nmax)O(N_{\max})O(Nmax)
- 每个测试用例:O(M−K)O(M-K)O(M−K)
- 总复杂度:O(Nmax+T⋅N)O(N_{\max} + T \cdot N)O(Nmax+T⋅N),在 N≤1000N \leq 1000N≤1000 时完全可行。
参考代码
// Arrange the Numbers
// UVa ID: 11481
// Verdict: Accepted
// Submission Date: 2025-11-05
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <iostream>
#include <vector>
using namespace std;
const int MOD = 1000000007;
const int MAXN = 1000;
long long fact[MAXN + 5], invFact[MAXN + 5];
// 快速幂取模
long long modPow(long long a, long long b, long long mod) {
long long res = 1;
while (b > 0) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
// 预计算阶乘和逆元阶乘
void precompute() {
fact[0] = 1;
for (int i = 1; i <= MAXN; i++) {
fact[i] = fact[i - 1] * i % MOD;
}
invFact[MAXN] = modPow(fact[MAXN], MOD - 2, MOD);
for (int i = MAXN - 1; i >= 0; i--) {
invFact[i] = invFact[i + 1] * (i + 1) % MOD;
}
}
// 组合数 C(n, r) 模 MOD
long long nCr(int n, int r) {
if (r < 0 || r > n) return 0;
return fact[n] * invFact[r] % MOD * invFact[n - r] % MOD;
}
// 计算答案
long long solve(int N, int M, int K) {
if (K > M) return 0;
int R = M - K; // 需要错排的数量
long long res = 0;
for (int i = 0; i <= R; i++) {
long long sign = (i % 2 == 0) ? 1 : -1; // 容斥符号
long long term = nCr(R, i) * fact[N - K - i] % MOD;
res = (res + sign * term + MOD) % MOD;
}
res = res * nCr(M, K) % MOD;
return res;
}
int main() {
precompute();
int T;
cin >> T;
for (int tc = 1; tc <= T; tc++) {
int N, M, K;
cin >> N >> M >> K;
cout << "Case " << tc << ": " << solve(N, M, K) << endl;
}
return 0;
}
总结
本题的关键在于将问题分解为选择固定位置和部分错排两个部分,并利用容斥原理计算部分错排的数量。
预计算阶乘和逆元可以高效计算组合数和排列数,使得每个测试用例可以在 O(M−K)O(M-K)O(M−K) 时间内解决。
443

被折叠的 条评论
为什么被折叠?



