题意:
有一棵大小为 n 的无标号无根树,其中有三种结点:红蓝黄。
红色结点的度数最多为 4,蓝、黄结点的度数最多为 3。
黄色结点之间不能有直接连边。
问方案数,mod 998244353,n≤10000。
思路:
无根树同构问题,一般是转化为以重心为根,然后按照有根树来做,这道题也是这样。
我们先考虑一个 O(n3) 的 暴力解法,每次我们枚举根的度数和颜色,每个子树的大小。
然后我们发现,对于大小相同的子树,实际上方案数也是相同的,这就启发我们对子树的大小进行 DP。
我们令 f0/1/2, i 表示当根是红/蓝/黄,子树大小为 i 时,方案数是多少。
显然这很难直接转移,因此我们还要定义两个数组:
rbd, i 表示根的度数为 d=0/1/2/3/4,子树大小为 i 的方案数,且此时根应为红色或蓝色(其实当 d=4 就特指根为红色)。
yed, i 表示根的度数为 d=0/1/2/3,子树大小为 i 的方案数,且此时根的颜色应为黄色。
假设此时我们处理到子树大小为 i 的情况,
首先我们先更新 f 数组:
f0, i=3∑d=0rbd, i
f1, i=2∑d=0rbd, i
f2, i=2∑d=0yed, i
为什么 f 不能更新满呢?因为我们要用 f 去更新 rb,ye,那么要求 f 里的方案数都至少能再连出一条边。
这时候我们已经计算完大小为 i 的不同子树的方案数,我们就可以去更新 rb,ye 数组:
我们以更新 rb 为例,假设要更新 rbj,z:
我们枚举选出 w 个大小为 i 的子树,方案数显然为 (tot+w−1w)
其中 tot=f0,i+f1,i+f2,i
那么就有转移:
fj,z←fj−w,z−i∗w×(tot+w−1w)
ye 数组的更新同理,不过要注意 tot=f0,i+f1,i,因为黄色不能与黄色连边。
因为重心的子树的大小不超过 ⌊n2⌋,因此我们的 f 只需要更新到 ⌊n2⌋。
最后我们用 rbd,n 和 yed,n 来计算最终的答案。
但有一个细节:对于 n 为偶数的情况,可能会出现两个重心的情况(就是某一条边连接的两棵子树大小都为 n2),这时候我们需要去重。
一些具体细节见代码。
代码
#include<bits/stdc++.h>
#define LL long long
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
inline int rd()
{
int sign = 1, re = 0; char c = getchar();
while(!isdigit(c)){if(c == '-') sign = -1; c = getchar();}
while(isdigit(c)){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
namespace MOD
{
int mod;
inline int add(int a, int b) {return a + b >= mod ? a + b - mod : a + b;}
inline int mul(int a, int b) {return 1ll * a * b % mod;}
inline int sub(int a, int b) {return a - b < 0 ? a - b + mod : a - b;}
inline int fast_pow(int a, int b = mod - 2)
{
int re = 1;
while(b)
{
if(b & 1) re = mul(re, a);
a = mul(a, a);
b >>= 1;
}
return re;
}
} using namespace MOD;
int n, f[3][3005], inv[5];
int rb[5][3005], ye[4][3005];
int C[5];
signed main()
{
n = rd(), mod = rd();
inv[1] = 1; FOR(i, 2, 4) inv[i] = mul(sub(mod, mod / i), inv[mod % i]);
rb[0][1] = ye[0][1] = 1;
FOR(i, 1, n >> 1)
{
FOR(j, 0, 2)
f[0][i] = add(f[0][i], rb[j][i]),
f[1][i] = add(f[1][i], rb[j][i]),
f[2][i] = add(f[2][i], ye[j][i]);
f[0][i] = add(f[0][i], rb[3][i]);
int tot = add(f[0][i], f[1][i]);
C[1] = tot;
FOR(j, 2, 3) C[j] = mul(C[j - 1], mul(tot + j - 1, inv[j]));
ROF(j, 3, 1) ROF(z, n, i + 1) for(int w = 1, mx = z / i; w <= mx && w <= j; w++)
ye[j][z] = add(ye[j][z], mul(ye[j - w][z - i * w], C[w]));
tot = add(tot, f[2][i]);
C[1] = tot;
FOR(j, 2, 4) C[j] = mul(C[j - 1], mul(tot + j - 1, inv[j]));
ROF(j, 4, 1) ROF(z, n, i + 1) for(int w = 1, mx = z / i; w <= mx && w <= j; w++)
rb[j][z] = add(rb[j][z], mul(rb[j - w][z - i * w], C[w]));
}
int ans = 0;
FOR(i, 0, 3) ans = add(ans, add(mul(rb[i][n], 2), ye[i][n]));
ans = add(ans, rb[4][n]);
if(!(n & 1))
{
int t = add(f[0][n >> 1], f[1][n >> 1]);
if(t & 1) t = add(mul(t, (t - 1) >> 1), mul(t, f[2][n >> 1]));
else t = add(mul(t - 1, t >> 1), mul(t, f[2][n >> 1]));
ans = sub(ans, t);
}
printf("%d", ans);
return 0;
}
GJ 那里需要开火车头才能通过;
Luogu 原题的范围是 n≤3000 的,大家可以到那里去提交一下。
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理