2022.10.3-C 旅伴

题意:

有一棵大小为 n 的无标号无根树,其中有三种结点:红蓝黄。

红色结点的度数最多为 4,蓝、黄结点的度数最多为 3

黄色结点之间不能有直接连边。

问方案数,mod 998244353n10000


思路:

无根树同构问题,一般是转化为以重心为根,然后按照有根树来做,这道题也是这样。

我们先考虑一个 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=d=03rbd, i

f1, i=d=02rbd, i

f2, i=d=02yed, i

为什么 f 不能更新满呢?因为我们要用 f 去更新 rb,ye,那么要求 f 里的方案数都至少能再连出一条边。

这时候我们已经计算完大小为 i 的不同子树的方案数,我们就可以去更新 rb,ye 数组:

我们以更新 rb 为例,假设要更新 rbj,z

我们枚举选出 w 个大小为 i 的子树,方案数显然为 (tot+w1w)

其中 tot=f0,i+f1,i+f2,i

那么就有转移:

fj,zfjw,ziw×(tot+w1w)

ye 数组的更新同理,不过要注意 tot=f0,i+f1,i,因为黄色不能与黄色连边。

因为重心的子树的大小不超过 n2,因此我们的 f 只需要更新到 n2

最后我们用 rbd,nyed,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]); // 更新 ye 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])); // 更新 rb 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 原题的范围是 n3000 的,大家可以到那里去提交一下。


__EOF__

本文作者zuytong
本文链接https://www.cnblogs.com/zuytong/p/16793571.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zuytong  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示