UOJ Logo zhouzixuan的博客

博客

bzoj 4015: [FJOI2014]树的重心

2016-02-13 22:36:50 By zhouzixuan
【题目描述】:
    给定一个n个点的树,每个点的编号从1至n,问这个树有多少不同的连通子树,和这个树有相同的重心。
其中n个点的树指的是n个点的最小连通图,显然n个点的树有n-1条边,去掉这n-1条边中的任何一条,原图都不再联通,任意两个点之间由唯一一条路径相连。
    对于一个树,树的重心定义为:删掉某点i后,若剩余k个连通分量,那么定义d(i)为这些连通分量中点的个数的最大值,所谓重心,就是使得d(i)最小的点i。
    基于以上定义,一个树的重心可能会有一个或者两个,题中所要求的联通子树,其重心编号和个数必须和原树的完全一样。
    找出给定的树中有多少联通的子树和这个树有相同的重心。
【题目描述】:
    根据重心的性质我们知道:如果重心的所有子树s1 s2 s3 ... sk,假设s1为最大
    1.若2*s1<n,那么重心唯一
    2.若2*s1=n,那么重心有两个且相邻
    所以我们求出所有重心后,分情况讨论
    1.如果重心是唯一的,我们把重心提根
    现在我们需要选取子树使得重心唯一且为根节点
    我们枚举一下选取子树大小,设为Size,那么需要根节点所有子树的大小s满足2*s1<Size,且s1+...+sk=Size-1
    我们令:
    f[i][j]表示从以i为根的子树选取j个结点的连通子树个数
    g[i][j]表示当前的子树前i个子节点中选取j个结点的连通子树
    g[i][j]=sigma(g[i-1][k]*f[son[i]][j-k])
    f[i][j]=g[SonNum][j]
    然后对于根节点,我们特殊处理一下,即在处理g数组的时候使得任何一棵子树大小满足2*s1<Size即可
    2.如果重心有两个,那么他们一定相邻,我们将两个重心之间边切去,那么变成两棵树
    我们需要从两个树中选取大小相同的两颗子树(注意一定要连着重心)
    我们依然令:
    f[i][j]表示从以i为根的子树选取j个结点的连通子树个数
    g[i][j]表示当前的子树前i个子节点中选取j个结点的连通子树
    g[i][j]=sigma(g[i-1][k]*g[son[i]][j-k])
    f[i][j]=g[SonNum][j]
    然后枚举两边子树大小乘起来即可
    时间复杂度O(N^3*Q)
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=205;
const int M=405;
const int maxmod=10007;
const int INF=32083208;
struct edge {int x,next;} b[M];
int cases,n,tot,a[N],f[N][N],g[N][N];
int cnt,root[3],size[N],rtsize,ans,caseid;
inline long long getint()
{
    long long x=0; char c=getchar(); bool flag=false;
    while ((c!='-')&&((c<'0')||(c>'9'))) c=getchar();
    if (c=='-') flag=true,c=getchar();
    while ((c>='0')&&(c<='9')) x=x*10+(long long)(c-'0'),c=getchar();
    if (flag) return -x; else return x;
}
inline void updata(int &a,int b)
{
    a+=b; if (a>=maxmod) a-=maxmod;
}
inline void addedge(int x,int y)
{
    ++tot;
    b[tot].x=y;
    b[tot].next=a[x];
    a[x]=tot;
}
void init()
{
    n=getint(); tot=cnt=0;
    memset(a,0,sizeof(a)); rtsize=INF;
    for (int i=1; i<=n-1; i++)
    {
        int x=getint(),y=getint();
        addedge(x,y); addedge(y,x);
    }
}
void dfs(int x,int fa)
{
    size[x]=1; int k=0;
    for (int p=a[x];p;p=b[p].next)
    {
        int pp=b[p].x; if (pp==fa) continue;
        dfs(pp,x); size[x]+=size[pp]; k=max(k,size[pp]);
    }
    k=max(k,n-size[x]);
    if (k<rtsize) cnt=1,root[1]=x,rtsize=k;
    else if (k==rtsize) root[++cnt]=x;
}
namespace One_Centre
{
    void dfs(int x,int fa)
    {
        for (int p=a[x];p;p=b[p].next) if (b[p].x!=fa) dfs(b[p].x,x);
        memset(g,0,sizeof(g)); f[x][0]=f[x][1]=1; size[x]=1; g[0][0]=1;
        for (int p=a[x],now=1;p;p=b[p].next,now++)
        {
            int pp=b[p].x; if (pp==fa) {now--; continue;} size[x]+=size[pp];
            for (int i=0; i<=size[x]; i++) for (int j=0; j<=i; j++) updata(g[now][i],g[now-1][i-j]*f[pp][j]%maxmod);
            for (int i=1; i<=size[x]; i++) f[x][i]=g[now][i-1];
        }
    }
    void solve()
    {
        memset(f,0,sizeof(f)); dfs(root[1],0); ans=1;
        for (int i=2; i<=n; i++)
        {
            memset(g,0,sizeof(g));
            int rt=root[1],nowans=0; size[rt]=1; g[0][0]=1;
            for (int p=a[rt],now=1;p;p=b[p].next,now++)
            {
                int pp=b[p].x; size[rt]+=size[pp];
                for (int j=0; j<=min(i-1,size[rt]); j++) 
                    for (int k=0; k<=j; k++) 
                    {
                        if (2*k>=i) break;
                        updata(g[now][j],g[now-1][j-k]*f[pp][k]%maxmod);
                    }
                nowans=g[now][i-1];
            }
            updata(ans,nowans);
        }
        printf("Case %d: %d\n",caseid,ans);
    }
}
namespace Two_Centre
{
    void dfs(int x,int fa)
    {
        for (int p=a[x];p;p=b[p].next) if (b[p].x!=fa) dfs(b[p].x,x);
        memset(g,0,sizeof(g)); f[x][0]=f[x][1]=1; size[x]=1; g[0][0]=1;
        for (int p=a[x],now=1;p;p=b[p].next,now++)
        {
            int pp=b[p].x; if (pp==fa) {now--; continue;} size[x]+=size[pp];
            for (int i=0; i<=size[x]; i++) for (int j=0; j<=i; j++) updata(g[now][i],g[now-1][i-j]*f[pp][j]%maxmod);
            for (int i=1; i<=size[x]; i++) f[x][i]=g[now][i-1];
        }
    }
    void solve()
    {
        memset(f,0,sizeof(f)); int rt=root[1],Rt=root[2];
        dfs(rt,Rt); dfs(Rt,rt); ans=0;
        for (int i=1; i<=n/2; i++) updata(ans,f[rt][i]*f[Rt][i]%maxmod);
        printf("Case %d: %d\n",caseid,ans);
    }
}
void solve()
{
    if (cnt==1) One_Centre::solve();
    if (cnt==2) Two_Centre::solve();
}
int main()
{
    cases=getint();
    while (cases--)
    {
        caseid++;
        init();
        dfs(1,0);
        solve();
    }
    return 0;
}

评论

nosta
@zhouzixuan 有点笔误,g[i][j]=sigma(g[i-1][k]*g[son[i]][j-k])应该是 g[i][j]=sigma(g[i-1][k]*f[son[i]][j-k])才对。另外ORZ。

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。