题解:洛谷 AT_abc425_e Count Sequences 2

【题目来源】

洛谷:AT_abc425_e [ABC425E] Count Sequences 2 - 洛谷

【题目描述】

给定一个正整数 \(N\) 和一个长度为 \(N\) 的正整数序列 \(C=(C_1, C_2, \ldots, C_N)\)

请你对于每组测试数据(共 \(T\) 组),求满足以下条件的正整数序列的数量,并对给定正整数 \(M\) 取模后输出:

  • 序列中所有元素都在 \(1\)\(N\) 之间(包含 \(1\)\(N\))。
  • 对于每个 \(i=1,2,\ldots,N\),数 \(i\) 在序列中恰好出现 \(C_i\) 次。

\(T\) 组测试数据共用同一个 \(M\)。请分别输出每组测试数据的答案。

【输入】

输入通过标准输入给出,格式如下:

\(T\) \(M\)
\(\mathrm{case}_1\)
\(\mathrm{case}_2\)
\(\vdots\)
\(\mathrm{case}_T\)

\(i\) 组测试数据,\(\mathrm{case}_i\),格式如下:

\(N\) \(C_1\) \(C_2\) \(\ldots\) \(C_N\)

【输出】

输出 \(T\) 行,每行一个答案。第 \(i\) 行输出第 \(i\) 组测试数据的答案。

【输入样例】

3 1000000000
2
2 2
5
1 1 1 1 1
6
1 2 3 4 5 6

【输出样例】

6
120
230379200

【核心思想】

  1. 问题分析:给定正整数 \(N\) 和序列 \(C=(C_1, C_2, \ldots, C_N)\),求满足条件的正整数序列数量:序列中每个元素在 \([1, N]\) 范围内,且数字 \(i\) 恰好出现 \(C_i\) 次。这是一个多重集排列/组合数递推问题,关键在于将问题转化为"依次放置每种数字的多重集排列计数",利用组合数的乘法原理逐步计算。

  2. 算法选择

    • 组合数递推预处理:用杨辉三角公式 \(C(n, k) = C(n-1, k) + C(n-1, k-1)\) 预处理组合数表,支持 \(O(1)\) 查询
    • 乘法原理分步计数:按数字 \(1\)\(N\) 的顺序依次放置,每次从剩余空位中选择 \(C_i\) 个位置放数字 \(i\)
  3. 关键步骤

    • 预处理组合数表\(i\)\(0\)\(5000\)\(j\)\(0\)\(i\)):
      • \(C(i, 0) = 1\)\(C(i, i) = 1\)
      • \(C(i, j) = (C(i-1, j) + C(i-1, j-1)) \% M\)
    • 处理每组测试数据
      • 读入 \(N\)\(C[1..N]\),计算前缀和 \(sa[i] = \sum_{j=1}^{i} C_j\)
      • 总长度 \(L = sa[N] = \sum_{j=1}^{N} C_j\)
      • 分步计算\(i\)\(1\)\(N\)):
        • 放置数字 \(i\) 时,剩余空位数为 \(L - sa[i-1]\)
        • 从中选 \(C_i\) 个位置:\(ans = ans \times C(L - sa[i-1],\ C_i) \% M\)
      • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:预处理 \(O(N_{\max}^2)\),每组数据 \(O(N)\)\(N_{\max} = 5000\)
    • 空间复杂度:\(O(N_{\max}^2)\),组合数表 \(5001 \times 5001\)
  5. 多重集排列的核心思想

    • 分步乘法原理:多重集的全排列数 = \(\frac{L!}{C_1! \cdot C_2! \cdots C_N!}\),等价于依次选择位置的组合数乘积
    • 组合数等价性\(C(L - sa[i-1],\ C_i) = \frac{(L - sa[i-1])!}{C_i! \cdot (L - sa[i])!}\),连乘后恰好消去中间项得到多重集排列公式
    • 离线预处理:多组测试共用模数 \(M\),预处理组合数表后每组 \(O(N)\) 查询
    • 适用于"多重集排列计数 + 多组查询"类问题,核心是将阶乘公式转化为可递推的组合数连乘

【算法标签】

普及 #排列组合

【代码详解】

#include <bits/stdc++.h>
using namespace std;

#define int long long  // 使用长整型
const int N = 5005;   // 定义组合数表的最大尺寸

int t, m, n;          // t:测试用例数, m:模数, n:当前测试用例的元素个数
int a[N], sa[N];      // a:存储输入数组, sa:前缀和数组
int c[N][N];          // 组合数表

signed main()
{
    // 输入测试用例数和模数
    cin >> t >> m;

    // 初始化组合数表
    c[0][0] = 1;  // C(0,0)=1
    c[1][0] = c[1][1] = 1;  // C(1,0)=C(1,1)=1

    // 预处理组合数表(递推计算)
    for (int i = 2; i <= 5000; i++)
    {
        c[i][0] = 1;  // C(i,0)=1
        for (int j = 1; j <= i; j++)
        {
            // 递推公式:C(i,j) = C(i-1,j) + C(i-1,j-1)
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % m;
        }
    }

    // 处理每个测试用例
    while (t--)
    {
        // 输入当前测试用例
        cin >> n;

        // 输入数组并计算前缀和
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
            sa[i] = sa[i - 1] + a[i];  // 计算前缀和
        }

        // 计算最终结果
        int ans = 1;
        for (int i = 1; i <= n; i++)
        {
            // 累乘组合数:C(sum[n]-sum[i-1], a[i])
            ans = ans * c[sa[n] - sa[i - 1]][a[i]] % m;
        }

        // 输出结果
        cout << ans << endl;
    }

    return 0;
}

【运行结果】

3 1000000000
2
2 2
6
5
1 1 1 1 1
120
6
1 2 3 4 5 6
230379200
posted @ 2026-08-27 11:02  团爸讲算法  阅读(8)  评论(0)    收藏  举报