【题目描述】:
给定一个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;
}