UVa 11481 Arrange the Numbers

题目描述

给定初始序列 1,2,3,…,N1, 2, 3, \dots, N1,2,3,,N,求有多少个排列满足:MMM 个位置中,恰好有 KKK 个数在它们原来的位置上(即排列后这些位置上的数字与初始序列相同)。

输入格式

  • 第一行一个整数 TTTT≤1000T \leq 1000T1000),表示测试用例数量。
  • 接下来 TTT 行,每行三个整数 N,M,KN, M, KN,M,K1≤N≤10001 \leq N \leq 10001N1000M≤NM \leq NMNK≤MK \leq MKM)。

输出格式

  • 对每个测试用例,输出 Case X: answer,其中 answer100000000710000000071000000007 取模。

题目分析

我们有一个长度为 NNN 的初始序列 1,2,…,N1, 2, \dots, N1,2,,N
我们要计算排列的数量,使得在MMM 个位置中,恰好有 KKK 个位置上的数字与初始序列相同,其余位置可以不同。


解题思路

这个问题可以拆分成两个部分:

  1. 选择固定的位置
    在前 MMM 个位置中,选择 KKK 个位置,这些位置上的数字必须与初始序列相同。
    选择方式数为组合数 (MK)\binom{M}{K}(KM)

  2. 安排剩余的数字
    剩下的 N−KN-KNK 个位置(包括前 MMM 个位置中没被选中的 M−KM-KMK 个位置,以及后 N−MN-MNM 个位置)必须满足:

    • MMM 个位置中,除了那 KKK 个固定位置外,剩下的 M−KM-KMK 个位置上的数字不能是它们原来的数字(即错排条件)。
    • N−MN-MNM 个位置没有限制,可以任意排列,但要考虑整体排列的合法性。

部分错排的容斥原理

R=M−KR = M - KR=MK(前 MMM 个位置中需要错排的数量)。
我们考虑在 N−KN-KNK 个位置上,前 RRR 个位置不能放它们原来的数字(这些数字来自初始序列的前 MMM 个位置中没被固定的那部分)。

定义 D(n,r)D(n, r)D(n,r) 表示 nnn 个元素的排列中,前 rrr 个位置是错排(即前 rrr 个位置上的元素都不是它们原来的元素),后 n−rn-rnr 个位置任意。

用容斥原理计算:
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=0r(1)i(ir)(ni)!
解释:

  • 选择 iii 个“坏事件”(前 rrr 个位置中恰好有 iii 个在原来的位置上),剩下的 n−in-ini 个位置任意排列。
  • 容斥原理求和即可。

最终公式

对于给定的 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(NK,MK)
其中
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=0r(1)i(ir)(ni)!

109+710^9+7109+7 计算。


算法步骤

  1. 预计算阶乘 fact[i]fact[i]fact[i] 和逆元阶乘 invFact[i]invFact[i]invFact[i]Nmax⁡=1000N_{\max} = 1000Nmax=1000
  2. 对每个测试用例:
    • 如果 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=0MK(1)i(iMK)×(NKi)!
      109+710^9+7109+7
  3. 输出结果。

复杂度分析

  • 预计算阶乘和逆元:O(Nmax⁡)O(N_{\max})O(Nmax)
  • 每个测试用例:O(M−K)O(M-K)O(MK)
  • 总复杂度:O(Nmax⁡+T⋅N)O(N_{\max} + T \cdot N)O(Nmax+TN),在 N≤1000N \leq 1000N1000 时完全可行。

参考代码

// 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(MK) 时间内解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值