题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 名同学和 \(M\) 种奖品,第 \(i\) 种奖品有 \(a_i\) 个。每位同学恰好分到一个奖品,剩余奖品不超过 \(1\) 个(即 \(\sum a_i = N\)\(N+1\))。求分配方案数对 \(10^9+7\) 取模。这是一个多重集排列问题,核心在于将"为每位同学分配奖品种类"转化为"将 \(N\) 个位置分配给 \(M\) 种颜色"的组合计数。

  2. 算法选择

    • 组合数递推预处理 + 乘法原理:预处理组合数 \(C(n,k)\),然后按顺序为每种奖品选择位置,利用乘法原理累乘方案数
  3. 关键步骤

    • 预处理组合数\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N_{max}^2 + T \cdot M)\),预处理组合数 \(O(N_{max}^2)\),每个测试用例 \(O(M)\)
    • 空间复杂度:\(O(N_{max}^2)\),组合数表
  5. 多重集排列与组合数的核心思想

    • 位置选择模型:将 \(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
posted @ 2026-08-26 11:54  团爸讲算法  阅读(12)  评论(0)    收藏  举报