AT_abc405_e 题解
LucasAoSaic · · 题解
问题描述
给定四种水果的数量:
- A 个苹果
- B 个橙子
- C 个香蕉
- D 个葡萄
要求将这
- 每个苹果必须排在每个香蕉之前;
- 每个苹果必须排在每个葡萄之前;
- 每个橙子必须排在每个葡萄之前。
果物之间同种不可区分,所有排列结果取模
核心思想与公式推导
由于题目条件:
- 条件 (2) 和 (3) 要求所有苹果和橙子在所有葡萄之前,
- 条件 (1) 要求所有苹果必须位于香蕉之前。
我们可以将整个排列分为两部分:
- 左侧部分:包含所有苹果、所有橙子以及部分香蕉。
- 右侧部分:包含所有葡萄以及剩余的香蕉。
设左侧放香蕉数为
左侧部分的排列
左侧部分包含:
- 苹果:
A 个 - 橙子:
B 个 - 左侧香蕉:
x 个
由于苹果必须全部在香蕉之前,所以苹果和香蕉的相对顺序固定(先
右侧部分的排列
右侧部分包含:
- 葡萄:
D 个 - 右侧香蕉:
C - x 个
为满足条件“所有橙子(及苹果)必须在葡萄之前”,右侧部分的第一个位置必须是葡萄。固定后,剩余的葡萄数为
总排列数
把左侧和右侧部分的方法数相乘后,对
当
详细代码(带注释)
下面是完整的 C++ 代码,代码中利用预处理阶乘和逆阶乘(使用快速幂法计算模逆元),从而快速求组合数。注释部分详细说明了每一步的含义。
#include <iostream>
using namespace std;
typedef long long ll;
const int MOD = 998244353;
// 预处理数组大小,取值足够覆盖题目最大规模,这里取 MAXN = 3000000 + 10
const int MAXN = 3000000 + 10;
int a, b, c, d; // 分别代表苹果、橙子、香蕉、葡萄的数量
ll fact[MAXN], invfact[MAXN];
// 快速幂函数,计算 base^exp mod MOD
inline ll quickPow(ll base, ll exp)
{
ll cnt = 1;
while (exp)
{
if (exp & 1)
{
cnt = cnt * base % MOD;
}
base = base * base % MOD;
exp >>= 1;
}
return cnt;
}
// 预处理 0 到 n 的阶乘和逆阶乘数组
inline void precompute(int n)
{
fact[0] = 1;
for (int i = 1; i <= n; ++ i)
{
fact[i] = fact[i - 1] * i % MOD;
}
// 利用费马小定理计算 fact[n] 的逆元
invfact[n] = quickPow(fact[n], MOD - 2);
// 反向递推计算 invfact 数组,从 n 到 0
for (int i = n; i >= 0; -- i)
{
if (i)
{
invfact[i - 1] = invfact[i] * i % MOD;
}
}
}
// 计算组合数 C(n, k) mod MOD(n 选 k)
inline ll cnm(int n, int k)
{
if (k < 0 || k > n)
{
return 0;
}
return fact[n] * invfact[k] % MOD * invfact[n - k] % MOD;
}
int main()
{
// 读入水果数量:a=苹果, b=橙子, c=香蕉, d=葡萄
scanf("%d%d%d%d", &a, &b, &c, &d);
// 计算预处理上界:
// 左侧部分最多需要 a+b+c 项;右侧部分需要的最大项为 c+d-1,
// 取两者较大值作为预处理范围。
int n1 = a + b + c,
n2 = c + d - 1,
nmax = n1 > n2 ? n1 : n2;
// 预处理阶乘与逆阶乘数组,范围 [0, nmax]
precompute(nmax);
ll ans = 0;
// 当没有葡萄时,仅考虑苹果、橙子、香蕉的排序
if (d == 0)
{
ans = cnm(a + b + c, b);
}
else
{
// 枚举 x:放在左侧的香蕉数,从 0 到 c
for (int x = 0; x <= c; x++)
{
// 左侧部分:
// 包含 a 个苹果、b 个橙子和 x 个香蕉
// 由于苹果必须在香蕉前面,其排列方案数为从 (a+b+x) 个位置中选出 b 个位置放橙子
ll ways1 = cnm(a + b + x, b);
// 右侧部分:
// 包含剩余的香蕉(c - x)个和 d 个葡萄,
// 且为满足条件,右侧部分的第一个位置必须为葡萄,
// 剩下的有 (c-x) + (d-1) 个位置,从中选择 (d-1) 个放葡萄
ll ways2 = cnm((c - x) + d - 1, d - 1);
// 将左右两部分方案数的乘积累加到答案中
ans = (ans + ways1 * ways2) % MOD;
}
}
// 输出答案(模 MOD)
printf("%lld\n", ans);
// 此处 cin >> a; 可用于调试时暂停,但在提交时可以删除
cin >> a;
return 0;
}