HDU 4863 Centroid of a Tree (树形dp)

来源:互联网 发布:淘宝数据表格 编辑:程序博客网 时间:2024/06/14 14:55

Centroid of a Tree

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 452    Accepted Submission(s): 208

Problem Description

Given a tree T with N vertices, your task is calculating the number of the connected sub-tree of T, which has the same centroid as T.
In order to define the centroid, some integer value will be associated to every vertex. Let's consider the vertex k. If we remove the vertex k from the tree (along with its adjacent edges), the remaining graph will have only N-1 vertices and may be composed of more than one connected components. Each of these components is (obviously) a tree. The value associated to vertex k is the largest number of vertices contained by some connected component in the remaining graph, after the removal of vertex k. All the vertices for which the associated value is minimum are considered centroids.
A graph can have an arbitrary number of centroids. However,it can be proved that for trees, there are only two possibilities:
1. The tree has precisely one centroid.
2. The tree has precisely two centroids. In this case, the two centroids are adjacent.
Note that in case 2, we just consider that T has two centroids, you should only count the sub-tree which has the two same centroids as T.

Input

The first line of the date is an integer T, which is the number of the text cases.
Then T cases follow each case starts of a number N descript above.
Then N-1 lines follow, each line contains two integers x and y, which means that there is a edge between x and y in tree T, you can assume that the index of T is from 1 to N.
1 <= T <= 50, 1 <= N <= 200,

Output

For each case, output the case number first, then output the number of the connected sub-tree which has the same centroid as T.
Give your answer modulo 10007.

Sample Input

5121 231 21 341 21 31 451 21 31 44 5

Sample Output

Case 1: 1Case 2: 1Case 3: 2Case 4: 5Case 5: 6

Author

FZU

Source

2014 Multi-University Training Contest 1



        少见HDU的题解网上居然少的可怜,仅有的几篇解释的都不太清楚……

        于是自己花了一个晚上的时间理解别人的代码,发现别人的代码虽然是对的,但是写的确实难以理解,所以我决定挑起这个重担,把这道题目解释清楚。

        首先,这道题要求子树的重心与原来一样,然后如果原来是两个重心,那么子树同样也得是这两个重心。那么我们先考虑简单的情况,原来的重心假设有两个。由于有两个重心,那么以这两个重心为根的子树的节点数一定相等,即若把连接两个重心的边删除后,树会被分成两棵节点数目相同的树。这个很容易理解,如果不相等,那么重心就不是那两个。这样的话,方案数就是sigma(w[root[0]][i]*w[root[1]][i])(1<i<=n/2),其中w[i][j]表示,以i为根有j个节点的子树的个数。

        然后,重心为一个的情况就比较复杂了,于是我们就反过来算,用总的子树个数减去不合法的子树个数,即重心不为原来树的重心的个数。那么,这个怎么计算呢?所谓不合法,重心改变了,即原重心的某个儿子的节点数sonsize大于其他节点个数之和。如果满足这个,至少对于原重心的儿子son,它的最大儿子节点数是sonsize,而原重心的最大儿子节点数则是sonsize+1,显然已经不合法。我们就通过这个算不合法的方案数。对于某一个儿子y,很容易想到表达式:方案数=sigma(w[y][i]*t[i-1])(2<i<=size[y]),其中t[i]表示节点数小于等于i且不包含y及y的后代的子树个数。这个式子比较形象的求得了所有不合法的方案数。

        那么现在的问题就是,如何求w数组还有t数组。对于w数组,我们就用树形dp求,转移方程:w[x][i]=sigma(w[x][i-j]*w[y][j]),其中y为x的儿子,sigma是关于j求和,然后i的枚举一定要倒序枚举,这个和01背包类似,如果正序枚举会重复计算。然后t数组也不难求,我们先预先设置一个dp数组,对于重心rt的一个儿子y,使得dp[i]=w[rt][i],此时的dp数组表示节点数为i的子树的个数。我们继续dp转移求不包含该儿子y及它的后代且含节点数为i的个数。有转移方程:dp[i]=dp[i]-dp[i-j]*w[y][j],此式中的dp[i-j]是已经计算完后的节点数为i-j且不包含y及y的后代的子树的方案数。这样转移完之后,我们就可以的到dp数组。然后,我们求t数组。注意到,t[i]和dp[i]的关系,可以得出t[i]=t[i-1]+dp[i+1],其中t[0]=0。这样我们就完成了全部的计算,按照不同情况分类讨论即可。

        其实关于t[i]为什么是与dp[i+1]关联,而不是dp[i]关联,我也有点不清楚,盼大神指点orz……具体见代码:

#include<iostream>#include<cstdio>#include<algorithm>#include<cstdlib>#include<cstring>#include<cmath>#include<iomanip>#include<vector>#include<queue>#define mod 10007#define N 210using namespace std;int son[N],root[2],ls[N],rootson,n,e;int w[N][N],dp[N],t[N];struct Edge{    int x,y,nxt;} g[N<<1];inline void addedge(int x,int y){    g[++e].x=x;    g[e].y=y;    g[e].nxt=ls[x];    ls[x]=e;}void findcent(int x,int fa)//找树的重心{    son[x]=1;    int maxson=0;    for(int i=ls[x];i;i=g[i].nxt)    {        int y=g[i].y;        if (y!=fa)        {            findcent(y,x);            son[x]+=son[y];            maxson=max(maxson,son[y]);        }    }    maxson=max(maxson,n-son[x]);    if (maxson<rootson)    {        rootson=maxson;        root[0]=x; root[1]=0;    } else    if (rootson==maxson) root[1]=x;}void dfs(int x,int fa)//树形dp求w数组{    son[x]=1;    w[x][0]=w[x][1]=1;//初始化    for(int i=ls[x];i;i=g[i].nxt)    {        int y=g[i].y;        if (y!=fa)        {            dfs(y,x);            son[x]+=son[y];        }    }    for(int i=ls[x];i;i=g[i].nxt)    {        int y=g[i].y;        if (y==fa) continue;        for(int j=son[x];j>1;j--)//一定要倒序枚举,不然会重复            for(int k=1;k<j&&k<=son[y];k++)            {                w[x][j]+=w[x][j-k]*w[y][k]%mod;                if (w[x][j]>=mod) w[x][j]%=mod;            }    }}int calculate1()//只有一个重心的情况{    int rt=root[0],res=0;    dfs(rt,-1);    for(int i=1;i<=n;i++)//先令res为所有方案    {        res+=w[rt][i];        if (res>=mod) res%=mod;    }    for(int i=ls[rt];i;i=g[i].nxt)    {        int y=g[i].y;        for(int j=1;j<=son[y];j++) dp[j]=w[rt][j];        for(int j=1;j<=son[y];j++)//计算dp数组,减去含有儿子y及其后代的方案            for(int k=1;k<j;k++)            {                dp[j]-=dp[j-k]*w[y][k];                if (dp[j]<0) dp[j]=(dp[j]%mod+mod)%mod;            }        t[0]=1;        for(int j=1;j<son[y];j++)//计算t数组            t[j]=(t[j-1]+dp[j+1])%mod;        for(int j=1;j<=son[y];j++)        {            res-=w[y][j]*t[j-1];//减去不合法方案数            if (res<0) res=(res%mod+mod)%mod;        }    }    return res;}int calculate2()//有两个重心的情况{    dfs(root[0],root[1]);    dfs(root[1],root[0]);    int res=0;    for(int i=1;i<=n/2;i++)    {        res+=w[root[0]][i]*w[root[1]][i]%mod;        if (res>=mod) res%=mod;    }    return res;}int main(){    int T_T,T;    cin>>T_T;T=T_T;    while(T_T--)    {        e=0;        scanf("%d",&n);        root[0]=root[1]=0;        memset(w,0,sizeof(w));        memset(ls,0,sizeof(ls));        for(int i=1;i<n;i++)        {            int u,v;            scanf("%d%d",&u,&v);            addedge(u,v);            addedge(v,u);        }        int ans=0;        rootson=n;        findcent(1,-1);        if (root[1]) ans=calculate2();                else ans=calculate1();        printf("Case %d: %d\n",T-T_T,ans);    }    return 0;}

原创粉丝点击