题目链接
题目描述
某公司计划从 份应聘简历中选出
到
人组成一个创新小组。请求出所有可能的小组组合数量,结果对
取模。
解题思路
本题是一个基础的组合计数问题。我们需要计算从 个人中选出
人、
人、
人的方案数之和。
问题分解
小组的人数可以是 、
或
人,这三种情况是互斥的。因此,总的组合数量是这三种情况的组合数之和:
其中 表示从
个不同元素中选出
个元素的组合数,模数
。
组合数计算
由于输入 的范围可以达到
,预计算所有阶乘的方案会占用过大的空间且没有必要。注意到我们需要计算的
值非常小(最大为7),我们可以采用一种更直接的计算方法。
组合数的计算公式可以写作:
在模算术下,这变为:
- 分子部分
:可以通过一个
次的循环来计算。
- 分母部分
:由于
很小,我们可以直接计算出
,然后利用费马小定理和快速幂算法求出其关于模
的乘法逆元。
算法流程
- 实现快速幂和逆元函数:编写
power(base, exp)
和modInverse(num)
函数,用于计算模逆元。 - 实现组合数函数:编写一个
combinations(n, k)
函数。- 处理边界情况:如果
或
,组合数为
。
- 用循环计算分子的累乘积。
- 用循环计算分母
。
- 计算
的逆元。
- 将分子和分母的逆元相乘,得到最终结果。
- 处理边界情况:如果
- 主逻辑:读入
,分别调用
combinations(n, 5)
,combinations(n, 6)
,combinations(n, 7)
,将结果相加并取模。
代码
#include <iostream>
#include <vector>
using namespace std;
long long power(long long base, long long exp) {
long long res = 1;
long long mod = 1000000007;
base %= mod;
while (exp > 0) {
if (exp % 2 == 1) res = (res * base) % mod;
base = (base * base) % mod;
exp /= 2;
}
return res;
}
long long modInverse(long long n) {
long long mod = 1000000007;
return power(n, mod - 2);
}
long long combinations(long long n, int k) {
if (k < 0 || k > n) {
return 0;
}
if (k == 0 || k == n) return 1;
if (k > n / 2) k = n - k;
long long mod = 1000000007;
long long numerator = 1;
for (int i = 0; i < k; ++i) {
numerator = (numerator * (n - i)) % mod;
}
long long denominator = 1;
for (int i = 1; i <= k; ++i) {
denominator = (denominator * i) % mod;
}
return (numerator * modInverse(denominator)) % mod;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
long long n;
cin >> n;
long long mod = 1000000007;
long long ans = 0;
ans = (ans + combinations(n, 5)) % mod;
ans = (ans + combinations(n, 6)) % mod;
ans = (ans + combinations(n, 7)) % mod;
cout << ans << endl;
return 0;
}
import java.util.Scanner;
public class Main {
static final int MOD = 1000000007;
public static long power(long base, long exp) {
long res = 1;
base %= MOD;
while (exp > 0) {
if (exp % 2 == 1) {
res = (res * base) % MOD;
}
base = (base * base) % MOD;
exp /= 2;
}
return res;
}
public static long modInverse(long n) {
return power(n, MOD - 2);
}
public static long combinations(long n, int k) {
if (k < 0 || k > n) {
return 0;
}
if (k == 0 || k == n) return 1;
if (k > n / 2) k = (int)(n - k);
long numerator = 1;
for (int i = 0; i < k; i++) {
numerator = (numerator * (n - i)) % MOD;
}
long denominator = 1;
for (int i = 1; i <= k; i++) {
denominator = (denominator * i) % MOD;
}
return (numerator * modInverse(denominator)) % MOD;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long n = sc.nextLong();
long ans = 0;
ans = (ans + combinations(n, 5)) % MOD;
ans = (ans + combinations(n, 6)) % MOD;
ans = (ans + combinations(n, 7)) % MOD;
System.out.println(ans);
}
}
MOD = 1000000007
def power(base, exp):
res = 1
base %= MOD
while exp > 0:
if exp % 2 == 1:
res = (res * base) % MOD
base = (base * base) % MOD
exp //= 2
return res
def mod_inverse(n):
return power(n, MOD - 2)
def combinations(n, k):
if k < 0 or k > n:
return 0
if k == 0 or k == n:
return 1
if k > n // 2:
k = n - k
numerator = 1
for i in range(k):
numerator = (numerator * (n - i)) % MOD
denominator = 1
for i in range(1, k + 1):
denominator = (denominator * i) % MOD
return (numerator * mod_inverse(denominator)) % MOD
n = int(input())
ans = 0
ans = (ans + combinations(n, 5)) % MOD
ans = (ans + combinations(n, 6)) % MOD
ans = (ans + combinations(n, 7)) % MOD
print(ans)
算法及复杂度
-
算法:组合数学、费马小定理、快速幂
-
时间复杂度:
combinations(n, k)
函数的复杂度由计算分子和分母的循环以及快速幂决定。循环次数为次,快速幂为
。由于
是一个很小的常数(最大为7),所以单次组合数计算的复杂度可以视为
。总时间复杂度为
。
-
空间复杂度:只使用了常数个变量,空间复杂度为
。