可爱的传送门酱在这里

题意:给你一棵最多 200 个点的树,求有多少个子联通块能使得它的重心和原来的树重合(如果原来的树有两个重心,那么子联通块也要有两个重心),共 50 组 case。

方法一:枚举,暴力,我会求重心

正解:一个点是树的重心,当且仅当它为根的时候,所有子节点为根的树的大小都小于等于总点数的一半,我们可以将相同重心的子树计数转换为满足这个性质的计数,当有两个根的时候,两个重心一定相邻,我们在它之间加一个点,两个重心都连向这个点并删除两重心之间的边,这样这个新点就是树的唯一一个重心了,最后统计答案的时候减掉一个就行。

这个时候我们得到了一棵有根树,需要求有多少个以原来的根为根的子树,去掉这个根后其它的联通块大小小于等于子树大小的一半。

因为这个是在根上的计数,我们不妨先把除根以外其它点为根时候的方案数算算

令 $f[u][i]$表示以 u 为根的子树有 i 个节点时候的方案数。

我们有:$f[u][i+j]+=f[u][i]\times f[v][j](v \in son[u])$

其中 i 是当前子树 u 的 size,j 是子树 v 的 size(这个会树形 dp 的应该都很熟悉了)

此刻我们已经完成了对 i 的计数,

我们再对根 (用 rt 表示) 进行计数,因为有子树大小不超过总结点数一半的约束条件 (子树指的是以 rt 的儿子为根的子树),我们需要在 dp 的时候就把这个约束设进去,因此我们假设 $g[i][j][k]$表示枚举到 rt 的第 i 个儿子时,子树最大是 j 个结点,总共有 k 个结点时候的方案数。于是我们开始闷声写方程

$g[i][j][k+l]+=g[i-1][j][k]*f[son[i]][l]$
这里 l 是枚举第 i 个儿子大小,k 是枚举前面子树的大小的,因此显然有 $l<=j$,而我们的 i 枚举的是根的儿子结点,在菊花图的时候也是 n 的复杂度,j 枚举的大小肯定是最大子树的大小,这样想想就 $O(n^4)$的复杂度了不就打出 gg 了吗?

我们可以将儿子结点按它们子树的大小从小到大排序,这样每次 j 就只需要枚举到当前子树的大小即可,相应的方程要改一改(具体看代码)。听说这样就能优化到 $O(n^3)$,然而我真的不会证复杂度(

#include<bits/stdc++.h>
#define fo(i, a, b) for (Re int i = (a); i <= (b); ++i)
#define fd(i, a, b) for (Re int i = (a); i >= (b); --i)
#define edge(i, u) for (Re int i = head[u], v = e[i].v; i; i = e[i].nxt, v = e[i].v)
#define N 205
#define Re register
#define pb push_back
#define F first
#define S second
#define ll long long
#define inf 1000000007
#define mp std::make_pair
#define lowbit(x) (x & -x)
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mod 10007
int n, T, head[N], tot, u, v;
struct edge{
    int nxt, v;
}e[N << 1];
int f[N][N], g[N][N][N];//f[i][j] 表示 i 点为根的有 j 个节点的联通块有多少点,g[i][j][k] 表示根的第 i 个儿子,最大的儿子有不超过 j 个节点,共 k 个节点的方案数,最后的时候用补集转换一下就能得到最大儿子有 j 个节点的方案数。
inline void addedge (int u, int v)
{
    e[++tot] = (edge) {head[u], v};
    head[u] = tot;
}
int sz[N], max[N];
inline void dfs (int u, int fa)
{
    sz[u] = 1;
    edge (i, u)
    {
        if (v == fa) continue;
        dfs(v, u);
        sz[u] += sz[v];
        max[u] = std::max(max[u], sz[v]);
    }
    max[u] = std::max(n - sz[u], max[u]);
}
int rt1, rt2, rt, mn, ans;
inline void findroot ()
{
    mn = inf;
    fo (i, 1, n) mn = std::min(mn, max[i]);
    rt1 = rt2 = rt = 0;
    fo (i, 1, n)
        if (mn == max[i])
        {
            if (!rt1)
                rt1 = i;
            else
                rt2 = i;
        }
    if (!rt2)
    {
        rt = rt1;
        ans = 1;//重心单独一个联通块
    }
    else
    {
        rt = ++n;//建立虚根?(其实就是让重心为 1 个)
        edge (i, rt1)
            if (v == rt2)
            {
                e[i].v = rt;
                break;
            }
        edge (i, rt2)
            if (v == rt1)
            {
                e[i].v = rt;
                break;
            }
        addedge(rt, rt1);
        addedge(rt, rt2);
    }
}
int h[N];
inline void treedp (int u, int fa)
{
    sz[u] = 1;//记得重新计算 u 点 sz 喵
    f[u][0] = f[u][1] = 1;
    edge (qwq, u)
    {
        if (v == fa) continue;
        treedp(v, u);
        memset(h, 0, sizeof h);
        fo (i, 1, sz[u])
            fo (j, 1, sz[v])
                h[i + j] = (h[i + j] + f[u][i] * f[v][j]) % mod;
        sz[u] += sz[v];
        fo (i, 1, sz[u]) f[u][i] = (f[u][i] + h[i]) % mod;
    }
}
int son[N], cnt, sum;
inline void init ()
{
    ans = 0;
    memset(f, 0, sizeof f);
    memset(g, 0, sizeof g);
    memset(max, 0, sizeof max);
    tot = 0;
    cnt = 0;
    memset(head, 0, sizeof head);
}
inline bool cmp (int x, int y)
{
    return sz[x] < sz[y];
}
int main()
{
    scanf("%d", &T);
    fo (QvQ, 1, T)
    {
        scanf("%d", &n);
        init();
        fo (i, 2, n)
        {
            scanf("%d %d", &u, &v);
            addedge(u, v);
            addedge(v, u);
        }
        dfs(1, 0);
        findroot();
        treedp(rt, 0);
        edge (i, rt)
            son[++cnt] = v;
        std::sort(son + 1, son + cnt + 1, cmp);
        sum = 0;
        fo (i, 0, 200) g[0][i][0] = g[0][i][1] = 1;
        fo (i, 1, cnt)
        {
            fo (j, 1, sz[son[i]])
                fo (k, 0, sum)
                    fo (l, 0, j)
                    {
                        g[i][j][k + l] = (g[i][j][k + l] + g[i - 1][std::min(j, sz[son[i - 1]])][k] * f[son[i]][l]) % mod;
                    }
            sum += sz[son[i]];
        }
        fo (i, 1, sz[son[cnt]])
            fo (j, i << 1, n)
                ans = (ans + g[cnt][i][j] - g[cnt][i - 1][j]) % mod;
        printf("Case %d: %d\n", QvQ, (ans + mod) % mod);
    }
    return 0;
}

参考资料:Bill Yang's blog

(讲真我觉得我的思路到 g 函数那里都是靠 Bill Yang 学长的 blog 的自己姿势水平太低了 QAQ)

分类: 文章

HomuraCat

明日は何になる? やがて君になる!

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注