专栏文章

题解: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,以为是道签到题结果两个小时才做出来,给我急死了。

首先因为 aa 的顺序不重要,所以把 aa 从大到小排序可以保证从前往后处理时,只有当前的 aia_i 才是唯一真正的限制(前面已经处理过的限制不会在因为 aia_i 而不合法之前就导致方案不合法)。这个 trick 应当是不难想到的。
然后是难点:发现这是一个 DP,并且设计出 DP 状态。(我是试出来的,大家不要学我。)
总之 DP 状态是:f(i,j)f(i, j) 表示ii 个人中,对其中 jj 个人分组的合法方案数
枚举 i,ji,j,对于 f(i,j)f(i, j) 的转移来源,首先考虑第 ii 个人是否被算到这 jj 个人当中。
如果不算,那么结果和 f(i1,j)f(i - 1, j) 是一样的。
如果算,那么枚举 ii 所在组的人数 kk,此时 jkj - k 就是选出的 jj 人中除去 ii 所在组的人数。
接下来需要一些组合数知识,我们先把这剩下的 (jk)(j - k) 人分好组,方案为 f(i1,jk)f(i - 1, j - k)。接着我们需要在剩下的 (i1(jk))(i - 1 - (j - k)) 人中选出 (k1)(k - 1) 个人与第 ii 个人拼成一组,方案数为 (i1(jk)k1)\binom{i - 1 -(j - k)}{k - 1}
这时事先把 aa 从小到大排序的作用就体现出来了,这可以保证当前正在处理的 aia_i 无论与前面的人怎么分组,这个 aia_i 都一定是组内最小的。只要让 kaik \le a_i,就能保证组内所有人的条件都能满足。
综上所述,总的状态转移方程如下:
f(i,j)=f(i1,j)+k=1min(ai,j)f(i1,jk)×(i1(jk)k1)f(i,j) = f(i-1,j) + \sum_{k=1}^{\min(a_i,j)} f(i - 1, j - k) \times \binom{i - 1 - (j - k)}{k - 1}
直接计算的时间复杂度是 O(n3)O(n^3),但此时已经可以通过这道题了:
CPP
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];
    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;
  }
}
但是模拟赛考场上毒瘤出题人搬题人不知道是不是为了卡 O(n3)O(n^3),把 nn 开到了 20002000,时间也缩减成了 11 秒,所以我还额外卡了下常:
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 条评论,欢迎与作者交流。

正在加载评论...