题解:洛谷 P10112 奖品分配
【题目来源】
洛谷:P10112 [GESP202312 八级] 奖品分配 - 洛谷
【题目描述】
班上有 \(N\) 名同学,学号从 \(0\) 到 \(N-1\)。有 \(M\) 种奖品要分给这些同学,其中,第 \(i\) 种奖品总共有 \(a_i\) 个(\(i=0,1, \cdots ,M-1\))。
巧合的是,奖品的数量不多不少,每位同学都可以恰好分到一个奖品,且最后剩余的奖品不超过 \(1\) 个(即:\(N\le a_0+a_1+ \cdots +a_{M-1}\le N+1\))。
现在,请你求出每个班级礼物分配的方案数,所谓方案,指的是为每位同学都分配一个种类的奖品。
只要有一位同学获得了不同种类的奖品,即视为不同的方案。方便起见,你只需要输出方案数对 \(10^{9}+7\) 取模后的结果即可。
共有 \(T\) 个班级都面临着奖品分配的问题,你需要依次为他们解答。
【输入】
第一行一个整数 \(T\),表示班级数量。
接下来 \(T\) 行,每行若干用单个空格隔开的正整数。首先是两个正整数\(N,M\),接着是 \(M\) 个正整数 \(a_0,a_1...a_{M-1}\)。保证 \(N \le a_0+a_1+\cdots+a_{M-1} \le N+1\)。
【输出】
输出 \(T\) 行,每行一个整数,表示该班级分配奖品的方案数对 \(10^{9}+7\) 取模的结果。
【输入样例】
3
3 2 1 2
3 2 1 3
5 3 1 3 1
【输出样例】
3
4
20
【核心思想】
-
问题分析:给定 \(N\) 名同学和 \(M\) 种奖品,第 \(i\) 种奖品有 \(a_i\) 个。每位同学恰好分到一个奖品,剩余奖品不超过 \(1\) 个(即 \(\sum a_i = N\) 或 \(N+1\))。求分配方案数对 \(10^9+7\) 取模。这是一个多重集排列问题,核心在于将"为每位同学分配奖品种类"转化为"将 \(N\) 个位置分配给 \(M\) 种颜色"的组合计数。
-
算法选择:
- 组合数递推预处理 + 乘法原理:预处理组合数 \(C(n,k)\),然后按顺序为每种奖品选择位置,利用乘法原理累乘方案数
-
关键步骤:
- 预处理组合数:\(C(i,j) = C(i-1,j) + C(i-1,j-1) \bmod (10^9+7)\)
- 处理每个测试用例:
- 读入 \(N\)、\(M\) 和 \(a[1..M]\)
- 计算总奖品数 \(sum = \sum a_i\)
- 确定初始可用位置数 \(t\):若 \(sum > N\) 则 \(t = N+1\),否则 \(t = N\)
- 按顺序分配位置(\(i\) 从 \(1\) 到 \(M\)):
- 从 \(t\) 个位置中选 \(a_i\) 个放第 \(i\) 种奖品:\(ans \leftarrow ans \times C(t, a_i) \bmod (10^9+7)\)
- 更新可用位置:\(t \leftarrow t - a_i\)
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N_{max}^2 + T \cdot M)\),预处理组合数 \(O(N_{max}^2)\),每个测试用例 \(O(M)\)
- 空间复杂度:\(O(N_{max}^2)\),组合数表
-
多重集排列与组合数的核心思想:
- 位置选择模型:将 \(N\) 名同学视为 \(N\) 个不同位置,分配奖品等价于为每种奖品选择对应数量的位置。第 \(i\) 种奖品有 \(a_i\) 个,从剩余 \(t\) 个位置中选 \(a_i\) 个,方案数为 \(C(t, a_i)\)
- 乘法原理的适用性:各种奖品的位置选择相互独立(选定一种奖品的位置后,剩余位置给下一种),总方案数为各步方案数的乘积
- 剩余奖品的处理:当 \(sum = N+1\) 时,总奖品比人数多 \(1\),意味着有 \(1\) 个奖品不分配。此时初始位置数 \(t = N+1\),最后会剩余 \(1\) 个位置(即 \(1\) 个奖品不分配),自然地处理了"剩余不超过 \(1\)"的约束
- 组合数的对称性:\(C(t, a_i) = C(t, t-a_i)\),选择 \(a_i\) 个位置放该奖品等价于选择 \(t-a_i\) 个位置不放该奖品,两种视角等价
- 适用于多重集排列、分步计数、组合数应用类问题
【解题思路】

【算法标签】
普及 #排列组合
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 将int宏定义为long long,防止组合数计算溢出
const int N = 1005; // 定义最大数组大小常量N为1005
const int mod = 1e9 + 7; // 定义模数mod为1e9+7
int T, n, m; // T为测试用例数,n为同学人数(总位置数),m为奖品种类数
int a[N]; // a[i]为第i种奖品的数量
int c[N][N]; // 组合数表c[n][m],即C(n,m)
int ans, sum; // ans为当前测试用例的答案,sum为所有奖品数量的总和
// 初始化组合数表(杨辉三角法预处理)
void init()
{
for (int i = 0; i < N; i++) // 枚举n从0到N-1
{
for (int j = 0; j <= i; j++) // 枚举m从0到n
{
if (j == 0)
{
c[i][j] = 1; // 边界条件:C(i,0)=1
}
else
{
// 组合数递推公式:C(i,j)=C(i-1,j)+C(i-1,j-1),对mod取模
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
}
}
signed main() // 使用signed main是因为#define int long long后,main返回值类型需要显式声明
{
// 预处理组合数表
init();
// 读入测试用例数
cin >> T;
while (T--) // 循环处理每个测试用例
{
// 读入同学人数n和奖品种类数m
cin >> n >> m;
// 初始化奖品总数为0
sum = 0;
// 读入每种奖品的数量
for (int i = 1; i <= m; i++) // 循环读入m种奖品
{
cin >> a[i]; // 读入第i种奖品的数量a[i]
sum += a[i]; // 累加计算所有奖品的总数
}
// 初始化答案为1(乘法单位元)
ans = 1;
int t; // t为当前剩余可用位置数
// 计算初始可用位置数
if (sum > n)
{
t = n + 1; // 如果奖品总数超过n,说明有1个奖品剩余,可用位置为n+1
}
else
{
t = n; // 如果奖品总数等于n,可用位置恰好为n
}
// 计算排列方案数:依次从剩余位置中为每种奖品选择放置位置
for (int i = 1; i <= m; i++) // 枚举每种奖品
{
// 从t个可用位置中选择a[i]个位置放第i种奖品
ans = (ans * c[t][a[i]]) % mod; // 累乘组合数,对mod取模
// 减少可用位置数(已放置a[i]个奖品)
t -= a[i];
}
// 输出当前测试用例的方案数
cout << ans << endl;
}
return 0;
}
【运行结果】
3
3 2 1 2
3
3 2 1 3
4
5 3 1 3 1
20
浙公网安备 33010602011771号