专栏文章
题解:AT_mujin_pc_2018_f チーム分け
AT_mujin_pc_2018_f题解参与者 5已保存评论 5
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 5 条
- 当前快照
- 1 份
- 快照标识符
- @mio24e12
- 此快照首次捕获于
- 2025/12/02 12:07 3 个月前
- 此快照最后确认于
- 2025/12/02 12:07 3 个月前
神经 NOIP 模拟赛把这题放 T1,以为是道签到题结果两个小时才做出来,给我急死了。
首先因为 的顺序不重要,所以把 从大到小排序可以保证从前往后处理时,只有当前的 才是唯一真正的限制(前面已经处理过的限制不会在因为 而不合法之前就导致方案不合法)。这个 trick 应当是不难想到的。
然后是难点:发现这是一个 DP,并且设计出 DP 状态。(我是试出来的,大家不要学我。)
总之 DP 状态是: 表示前 个人中,对其中 个人分组的合法方案数。
枚举 ,对于 的转移来源,首先考虑第 个人是否被算到这 个人当中。
如果不算,那么结果和 是一样的。
如果算,那么枚举 所在组的人数 ,此时 就是选出的 人中除去 所在组的人数。
接下来需要一些组合数知识,我们先把这剩下的 人分好组,方案为 。接着我们需要在剩下的 人中选出 个人与第 个人拼成一组,方案数为 。
这时事先把 从小到大排序的作用就体现出来了,这可以保证当前正在处理的 无论与前面的人怎么分组,这个 都一定是组内最小的。只要让 ,就能保证组内所有人的条件都能满足。
综上所述,总的状态转移方程如下:
直接计算的时间复杂度是 ,但此时已经可以通过这道题了:
CPPf[0][0] = 1;
for (int i = 1; i <= n; i++)
{
f[i][0] = 1, f[i][1] = i;
for (int j = 2; j <= i; j++)
{
f[i][j] = f[i - 1][j];
for (int k = 1; k <= min(a[i], j); k++)
(f[i][j] += f[i - 1][j - k] * C[i - 1 - (j - k)][k - 1]) %= MOD;
}
}
但是模拟赛考场上毒瘤出题人搬题人不知道是不是为了卡 ,把 开到了 ,时间也缩减成了 秒,所以我还额外卡了下常:
CPP#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
typedef unsigned long long ULL;
constexpr int MAXN = 1005;
constexpr ULL MOD = 998244353;
int n, a[MAXN];
ULL f[MAXN][MAXN], C[MAXN][MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= i; j++)
C[i][j] = j ? (C[i - 1][j] + C[i - 1][j - 1]) % MOD : 1;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1, greater<int>());
f[0][0] = 1;
for (int i = 1; i <= n; i++)
{
f[i][0] = 1, f[i][1] = i;
for (int j = 2; j <= i; j++)
{
f[i][j] = f[i - 1][j];
int ul = min(a[i], j);
for (int k = 1, cnt = 0; k <= ul; k++)
{
f[i][j] += f[i - 1][j - k] * C[i - 1 - (j - k)][k - 1];
if (++cnt == 17) f[i][j] %= MOD, cnt = 0;
}
f[i][j] %= MOD;
}
}
cout << f[n][n] << '\n';
return 0;
}
相关推荐
评论
共 5 条评论,欢迎与作者交流。
正在加载评论...