2023年12月GESP真题及题解(C++八级): 奖品分配

2023年12月GESP真题及题解(C++八级): 奖品分配

题目描述

班上有 N N N 名同学,学号从 0 0 0 到 N − 1 N-1 N−1。有 M M M 种奖品要分给这些同学,其中,第 i i i 种奖品总共有 a i a_i ai 个 ( i = 0 , 1 , ⋯   , M − 1 i=0,1, \cdots ,M-1 i=0,1,⋯,M−1)。

巧合的是,奖品的数量不多不少,每位同学都可以恰好分到一个奖品,且最后剩余的奖品不超过 1 1 1 个(即: N ≤ a 0 + a 1 + ⋯ + a M − 1 ≤ N + 1 N\le a_0+a_1+ \cdots +a_{M-1}\le N+1 N≤a0+a1+⋯+aM−1≤N+1)。

现在,请你求出每个班级礼物分配的方案数,所谓方案,指的是为每位同学都分配一个种类的奖品。

只要有一位同学获得了不同种类的奖品,即视为不同的方案。方便起见,你只需要输出方案数对 10 9 + 7 10^{9}+7 109+7 取模后的结果即可。

共有 T T T 个班级都面临着奖品分配的问题,你需要依次为他们解答。

输入格式

第一行一个整数 T T T,表示班级数量。

接下来 T T T 行,每行若干用单个空格隔开的正整数。首先是两个正整数 N , M N,M N,M,接着是 M M M 个正整数 a 0 , a 1 . . . a M − 1 a_0,a_1...a_{M-1} a0,a1...aM−1。保证 N \\le a_0+a_1+\\cdots+a_{M-1} \\le N+1

输出格式

输出 T T T 行,每行一个整数,表示该班级分配奖品的方案数对 10 9 + 7 10^{9}+7 109+7 取模的结果。

输入输出样例 1
输入 1
复制代码
3
3 2 1 2
3 2 1 3
5 3 1 3 1 
输出 1
复制代码
3
4
20
输入输出样例 2
输入 2
复制代码
5
100 1 100
100 1 101
20 2 12 8
123 4 80 20 21 3
999 5 101 234 499 66 99
输出 2
复制代码
1
1
125970
895031741
307187590
说明/提示

样例解释 1

对于第 1 1 1 个班级,学号为 0 , 1 , 2 0,1,2 0,1,2 的同学可以依次分别获得奖品 0 , 1 , 1 0,1,1 0,1,1,也可以依次分别获得奖品 1 , 0 , 1 1,0,1 1,0,1,也可以依次分别获得奖品 1 , 1 , 0 1,1,0 1,1,0 ,因此共有 3 3 3 种方案。

对于第 2 2 2 个班级,学号为 0 , 1 , 2 0,1,2 0,1,2 的同学可以依次分别获得奖品 0 , 1 , 1 0,1,1 0,1,1 ,也可以依次分别获得奖品 1 , 0 , 1 1,0,1 1,0,1,也可以依次分别获得奖品 1 , 1 , 0 1,1,0 1,1,0,也可以依次分别获得奖品 1 , 1 , 1 1,1,1 1,1,1,因此共有 4 4 4 种方案。

对于第 3 3 3 个班级,可以把编号为 0 0 0 的奖品分配给 5 5 5 名同学中的任意一名,共有 5 5 5 种方案;再把编号为 2 2 2 的奖品分配给剩余 4 4 4 名同学中的任意一名,共有 4 4 4 种方案;最后给剩余 3 3 3 名同学自然获得 1 1 1 号奖品。因此,方案数为 5 × 4 = 20 5 \times 4 = 20 5×4=20。

数据范围

对于 30 % 30\% 30% 的测试点,保证 N ≤ 10 N \le 10 N≤10。

对于另外 30 % 30\% 30% 的测试点,保证 M = 2 M=2 M=2。

对于所有测试点,保证 N ≤ 1000 N \le 1000 N≤1000;保证 T ≤ 1000 T \le 1000 T≤1000 ;保证 M ≤ 1001 M \le 1001 M≤1001。

思路分析

这道题的关键在于发现总奖品数S与人数N的关系,从而简化计算。当S=N时,必须全部分配,方案数为N!除以各奖品数量的阶乘;当S=N+1时,有一种奖品少分一个,方案数为(N+1)!除以各奖品数量的阶乘。由于模数为质数,可以用预处理阶乘和阶乘逆元来快速计算。

代码实现

cpp 复制代码
#include <bits/stdc++.h> // 万能头
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;   // 模数
const int MAXF = 1001;     // 最大阶乘数,因为N最大1000,N+1最大1001

ll fac[MAXF + 5];         // 阶乘数组
ll invfac[MAXF + 5];      // 阶乘逆元数组

// 快速幂取模
ll pow_mod(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

// 预处理阶乘和阶乘逆元
void init() {
    fac[0] = 1;
    for (int i = 1; i <= MAXF; i++) {
        fac[i] = fac[i - 1] * i % MOD;
    }
    // 费马小定理求最大阶乘的逆元
    invfac[MAXF] = pow_mod(fac[MAXF], MOD - 2);
    // 递推求其他阶乘逆元
    for (int i = MAXF; i >= 1; i--) {
        invfac[i - 1] = invfac[i] * i % MOD;
    }
}

int main() {
    init(); // 预处理
    int T;
    cin >> T;
    while (T--) {
        int N, M;
        cin >> N >> M;
        ll d = 1; // 保存分母的逆元乘积,即所有invfac[a_i]的积
        int S = 0;
        for (int i = 0; i < M; i++) {
            int a;
            cin >> a;
            S += a;
            d = d * invfac[a] % MOD; // 乘以当前a_i阶乘的逆元
        }
        ll ans;
        if (S == N) {
            ans = fac[N] * d % MOD; // S=N时,分子为N!
        } else { // S == N+1
            ans = fac[N + 1] * d % MOD; // S=N+1时,分子为(N+1)!
        }
        cout << ans << endl;
    }
    return 0;
}

功能分析

  1. 预处理阶乘和逆元:预先计算0到1001的阶乘及其逆元,便于后续O(1)查询。

  2. 快速幂取模:用于计算阶乘的逆元(费马小定理)。

  3. 主逻辑:对于每个测试用例:

    • 读入N、M和每种奖品的数量a_i。
    • 计算总奖品数S。
    • 计算分母的逆元乘积(所有invfac[a_i]的乘积)。
    • 根据S与N的关系选择分子(N!或(N+1)!),与分母逆元相乘取模得到答案。
  4. 复杂度

  • 时间复杂度:预处理O(MAXF log MOD),每个测试用例O(M),整体O(T*M),在数据范围内完全可以接受。
  • 空间复杂度:O(MAXF),用于存储阶乘和逆元表。

完整GESP C++考级真题题解专栏:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html

更多csp信奥赛C++学习资料汇总:

1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):

https://edu.csdn.net/lecturer/7901 点击跳转


2、CSP信奥赛C++竞赛拿奖视频课:

https://edu.csdn.net/course/detail/40437 点击跳转

3、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html

4、csp信奥赛冲刺一等奖有效刷题题解:

CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转

· 文末祝福 ·

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"跟着王老师一起学习信奥赛C++";
	cout<<"    成就更好的自己!       ";
	cout<<"  csp信奥赛一等奖属于你!   ";
	return 0;
}
相关推荐
智者知已应修善业2 小时前
【输入字符串不用数组回车检测转换连续数字为整数】2024-10-26
c语言·c++·经验分享·笔记·算法
头发还没掉光光2 小时前
解决TCP粘包问题,使用C++实现TCP通信的自定义协议设计
linux·网络·c++·网络协议·tcp/ip
比昨天多敲两行2 小时前
C++ 类和对象(中)
开发语言·c++
智者知已应修善业2 小时前
【整数各位和循环求在0-9范围】2024-10-27
c语言·c++·经验分享·笔记·算法
燃于AC之乐2 小时前
我的算法修炼之路--9——重要算法思想:贪心、二分、正难则反、多重与完全背包精练
c++·算法·贪心算法·动态规划·二分答案·完全背包·多重背包
txinyu的博客3 小时前
unique_ptr
linux·服务器·c++
炬火初现3 小时前
C++17特性(3)
开发语言·c++
晨非辰3 小时前
Linux权限实战速成:用户切换/文件控制/安全配置15分钟掌握,解锁核心操作与权限模型内核逻辑
linux·运维·服务器·c++·人工智能·后端
草莓熊Lotso3 小时前
Linux 进程创建与终止全解析:fork 原理 + 退出机制实战
linux·运维·服务器·开发语言·汇编·c++·人工智能