题解:洛谷 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
【核心思想】
-
问题分析:给定正整数 \(N\) 和序列 \(C=(C_1, C_2, \ldots, C_N)\),求满足条件的正整数序列数量:序列中每个元素在 \([1, N]\) 范围内,且数字 \(i\) 恰好出现 \(C_i\) 次。这是一个多重集排列/组合数递推问题,关键在于将问题转化为"依次放置每种数字的多重集排列计数",利用组合数的乘法原理逐步计算。
-
算法选择:
- 组合数递推预处理:用杨辉三角公式 \(C(n, k) = C(n-1, k) + C(n-1, k-1)\) 预处理组合数表,支持 \(O(1)\) 查询
- 乘法原理分步计数:按数字 \(1\) 到 \(N\) 的顺序依次放置,每次从剩余空位中选择 \(C_i\) 个位置放数字 \(i\)
-
关键步骤:
- 预处理组合数表(\(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\)
- 预处理组合数表(\(i\) 从 \(0\) 到 \(5000\),\(j\) 从 \(0\) 到 \(i\)):
-
时间/空间复杂度:
- 时间复杂度:预处理 \(O(N_{\max}^2)\),每组数据 \(O(N)\),\(N_{\max} = 5000\)
- 空间复杂度:\(O(N_{\max}^2)\),组合数表 \(5001 \times 5001\)
-
多重集排列的核心思想:
- 分步乘法原理:多重集的全排列数 = \(\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
浙公网安备 33010602011771号