题意:

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

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

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

问方案数,\(\bmod~998244353\)\(n\le10000\)


思路:

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

我们先考虑一个 \(O(n^3)\)暴力解法,每次我们枚举根的度数和颜色,每个子树的大小。

然后我们发现,对于大小相同的子树,实际上方案数也是相同的,这就启发我们对子树的大小进行 DP。

我们令 \(f_{0/1/2,~i}\) 表示当根是红/蓝/黄,子树大小为 \(i\) 时,方案数是多少。

显然这很难直接转移,因此我们还要定义两个数组:

\(rb_{d,~i}\) 表示根的度数为 \(d=0/1/2/3/4\),子树大小为 \(i\) 的方案数,且此时根应为红色或蓝色(其实当 \(d=4\) 就特指根为红色)。

\(ye_{d,~i}\) 表示根的度数为 \(d=0/1/2/3\),子树大小为 \(i\) 的方案数,且此时根的颜色应为黄色。

假设此时我们处理到子树大小为 \(i\) 的情况,

首先我们先更新 \(f\) 数组:

\[f_{0,~i}=\sum_{d=0}^3 rb_{d,~i} \]

\[f_{1,~i}=\sum_{d=0}^2 rb_{d,~i} \]

\[f_{2,~i}=\sum_{d=0}^2 ye_{d,~i} \]

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

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

我们以更新 \(rb\) 为例,假设要更新 \(rb_{j,z}\)

我们枚举选出 \(w\) 个大小为 \(i\) 的子树,方案数显然为 \(\begin{pmatrix} tot+w-1 \\ w \end{pmatrix}\)

其中 \(tot=f_{0,i}+f_{1,i}+f_{2,i}\)

那么就有转移:

\[f_{j,z}\leftarrow f_{j-w, z-i*w} \times \begin{pmatrix} tot+w-1 \\ w \end{pmatrix} \]

\(ye\) 数组的更新同理,不过要注意 \(tot=f_{0,i}+f_{1,i}\),因为黄色不能与黄色连边。

因为重心的子树的大小不超过 \(\lfloor \frac{n}{2} \rfloor\),因此我们的 \(f\) 只需要更新到 \(\lfloor \frac{n}{2} \rfloor\)

最后我们用 \(rb_{d,n}\)\(ye_{d,n}\) 来计算最终的答案。

但有一个细节:对于 \(n\) 为偶数的情况,可能会出现两个重心的情况(就是某一条边连接的两棵子树大小都为 \(\frac{n}{2}\)),这时候我们需要去重。

一些具体细节见代码。


代码

#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 原题的范围是 \(n\le 3000\) 的,大家可以到那里去提交一下。

原文地址:http://www.cnblogs.com/zuytong/p/16793571.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性