CSP-S 2024 第六次
A
排序之后只会选相邻的,直接 DP。
B
从前往后考虑每个数 \(a_i\) 要不要删。
若不删 \(a_i\):
- 若 \(a_i\ne 0\),则 \(a_i\) 已经确定。
- 若 \(a_i=0\),则 \(a_i\) 可取所有没出现过的数,以及 \(i\) 后最小的数(先删掉它再把 \(a_i\) 赋成它)
若删掉 \(a_i\):
- 若 \(a_{i+1}\ne 0\),则 \(a_i\) 会变成 \(a_{i+1}\)。
- 若 \(a_{i+1}=0\),则 \(a_{i+1}\) 可取所有没出现过的数(删除操作给 \(a_i\) 了,所以没法取 \(i+1\) 后最小的数),然后 \(a_i\) 会变成 \(a_{i+1}\)。
若不删 \(a_i\) 可以使 \(a_i\) 更小就不删,否则就删掉。
确定删掉的数之后,把没出现过的数从小到大填到空位里即可。
C
先把边权为 0 的边缩起来。
钦定 \(P_1\) 为根,则 \(i\) 的答案是 \(1\) 当且仅当 \(P\) 的前 \(i\) 个点形成的虚树恰有 \(i\) 个点。
用 set 维护虚树点集即可。
D
先设 \(g_{n,l,p}\) 表示有 \(n\) 个点,最大深度为 \(l\),有 \(p\) 个深度最大的点的无标号有根树个数。
可以用分组背包的形式转移,枚举 \(n,l,p,t\),每次加 \(t\) 个 \(g_{n,l,p}\) 内的树,有 \({g_{n,l,p}+t-1\choose t}\) 种方案(可重集组合数)。
对于 \(k\) 是偶数,钦定直径中点为根,则最大深度为 \(k/2\),每两个属于根的不同子树的、深度都为 \(k/2\) 的叶子可以产生一个直径。
再设 \(f_{n,p,d}\) 表示有 \(n\) 个点,有 \(p\) 个深度为 \(k/2\) 的叶子,有 \(d\) 个直径的无标号有根树个数,
转移同理,枚举 \(n,p,t\),每次加 \(t\) 个大小为 \(n\),有 \(p\) 个深度为 \(k/2\) 的叶子的子树(通过 \(g\) 得到方案数)即可。
对于 \(k\) 是奇数,在直径中边上加一个点,钦定这个点为根,
则根恰有两个子树,直径个数为两个子树中深度为 \(\left\lceil\dfrac k2\right\rceil\) 的叶子个数之积。
枚举两个子树的大小,深度为 \(\left\lceil\dfrac k2\right\rceil\) 的叶子个数,统计答案即可。
顶针要代码,那就放一下代码:
#include <cstdio>
#include <cstring>
#define M 998244353
#define int long long
bool F;
int n, k, p, o, v[50], g[50][50][50], G[50][50];
int C(int n, int k)
{
int q = 1;
for (int i = n + k - 1; i >= n; --i)
q = q * i % M;
for (int i = 1; i <= k; ++i)
q = q * v[i] % M;
return q;
}
signed main()
{
freopen("dia.in", "r", stdin);
freopen("dia.out", "w", stdout);
v[1] = 1;
for (int i = 2; i <= 40; ++i)
v[i] = (M - M / i) * v[M % i] % M;
scanf("%lld%lld%lld", &n, &k, &p);
if (k & 1)
F = 1;
k = k + 1 >> 1;
g[1][1][1] = 1;
for (int vn = 1; vn <= n; ++vn)
for (int vl = 1; vl <= k; ++vl)
for (int vp = 1; vp <= vn; ++vp)
for (int un = n - vn; un >= 1; --un)
for (int ul = 1; ul <= k; ++ul)
for (int up = 1; up <= un; ++up)
for (int t = 1; un + vn * t <= n; ++t)
{
int _ = C(g[vn][vl][vp], t);
if (vl + 1 > ul)
g[un + vn * t][vl + 1][vp * t] = (g[un + vn * t][vl + 1][vp * t] + g[un][ul][up] * _) % M;
else if (vl + 1 == ul)
g[un + vn * t][ul][up + vp * t] = (g[un + vn * t][ul][up + vp * t] + g[un][ul][up] * _) % M;
else
g[un + vn * t][ul][up] = (g[un + vn * t][ul][up] + g[un][ul][up] * _) % M;
}
for (int vn = 1; vn <= n; ++vn)
for (int vp = 1; vp <= vn; ++vp)
{
G[vn][vp] = (G[vn][vp] + g[vn][k][vp]) % M;
for (int vl = 1; vl < k; ++vl)
G[vn][0] = (G[vn][0] + g[vn][vl][vp]) % M;
}
if (!F)
{
int f[50][50][50];
memset(f, 0, sizeof f);
f[1][0][0] = 1;
for (int vn = 1; vn <= n; ++vn)
for (int vp = 0; vp <= vn; ++vp)
for (int un = n - vn; un >= 1; --un)
for (int ud = 0; ud <= p; ++ud)
for (int up = 0; up <= un && ud + up * vp <= p; ++up)
{
int _d = ud + vp * up, _p = up + vp;
for (int t = 1; un + vn * t <= n && _p <= n && _d <= p; ++t, _d += vp * _p, _p += vp)
{
int _ = C(G[vn][vp], t);
f[un + vn * t][_p][_d] = (f[un + vn * t][_p][_d] + f[un][up][ud] * _) % M;
}
}
int z = 0;
for (int i = 0; i <= n; ++i)
z = (z + f[n][i][p]) % M;
printf("%lld", z);
}
else
{
int z = 0;
for (int vn = 1; vn <= n; ++vn)
for (int vp = 0; vp <= vn; ++vp)
for (int un = 1; un <= n; ++un)
for (int up = 0; up <= un; ++up)
if (un + vn == n && up * vp == p)
{
int _z = z;
if (un == vn && up == vp)
z = (z + C(G[un][up], 2) * 2) % M;
else
z = (z + G[un][up] * G[vn][vp]) % M;
}
printf("%lld", z * (M + 1 >> 1) % M);
}
return 0;
}