题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3162
题目分析:一道很厉害的题,让我知道原来Hash还可以判断两棵树的形态是否相同。
这题的具体做法还是看VFK的题解吧,我表示只能orz。用简单的话概括一下题解的内容就是:
1.定义重心为树的直径的中点。如果树的直径长度为偶数,就在最中间的边上加一个虚点作为重心。
2.以重心为根变成有根树,然后对括号序列Hash+排序,判断两棵子树是否同构。
3.设某个点所有儿子的子树集合为
∑x(cx,nx)
∑
x
(
c
x
,
n
x
)
,其中
(ci,ni)
(
c
i
,
n
i
)
表示形态为
ci
c
i
的子树有
ni
n
i
棵。由于不同的形态的子树互不影响,所以问题变成了如何计算
(ci,ni)
(
c
i
,
n
i
)
的对当前节点的贡献。根据组合数,值域为
[1,n]
[
1
,
n
]
的
m
m
个数的组合方案为。
4.每个点设两个值
f,g
f
,
g
,分别表示选或不选这个点时,其子树的方案数。然后用组合数学+DP,分类讨论一下即可维护。
最后总结一下我已知道的关于组合数的一些东西:
①n个不同的球放进m个不同的盒子里:
mn
m
n
。
②n个相同的球放进m个不同的盒子里:将所有盒子和球任意打乱排成一列,并令最后一个必为盒子,然后每个盒子获得它前面一个盒子到它之间的球,最后对所有盒子从左到右编号为1~m。可知这样的方案数为
Cm−1n+m−1
C
n
+
m
−
1
m
−
1
。
③值域为[1,n]的m个数的组合方案数:可以直接看成m个相同的数分到编号为1~n的n个集合里,然后化为②。VFK的pdf里给出了另一种解释:令
a1≤a2……≤am
a
1
≤
a
2
…
…
≤
a
m
,则
a1+1<a2+2……<am+m
a
1
+
1
<
a
2
+
2
…
…
<
a
m
+
m
。每种后一个序列唯一对应前一个序列。于是变为值域在
[2,n+m]
[
2
,
n
+
m
]
中选m个不同的数,故方案数也是
Cmn+m−1
C
n
+
m
−
1
m
。
④n个相同的球放进m个相同的盒子里:贝尔数,即第二类stirling数的前缀和。
最后,说下这题的一些注意点。首先是Hash值排序时,要将大的值排前面,因为这样深度大的点权值会变大,不容易冲突。另外,判断两棵子树是否同构,不仅要Hash值相等,还要判 f,g f , g 值是否相等,这样可以降低冲突概率。原树的重心为一条边的时候,要注意判断左右两棵子树是否同构。这题的样例挺强的,过了样例应该就能1A了QAQ。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=500100;
const long long M=1000000007;
typedef long long LL;
struct edge
{
int obj;
edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;
LL Two[maxn];
LL nfac[maxn];
LL Hash_val[maxn];
int Len[maxn];
vector <int> Son[maxn];
LL f[maxn];
LL g[maxn];
int d[maxn];
int len;
int fa[maxn];
int dep[maxn];
int n;
void Add(int x,int y)
{
cur++;
e[cur].obj=y;
e[cur].Next=head[x];
head[x]=e+cur;
}
void Find(int node)
{
for (edge *p=head[node]; p; p=p->Next)
{
int son=p->obj;
if (son==fa[node]) continue;
fa[son]=node;
dep[son]=dep[node]+1;
Find(son);
}
}
bool Comp(int x,int y)
{
return (Hash_val[x]>Hash_val[y]);
}
bool Judge(int x,int y)
{
return ( Hash_val[x]==Hash_val[y] && f[x]==f[y] && g[x]==g[y] );
}
LL Get(LL n,LL m)
{
n=n+m-1LL;
LL ans=1;
for (LL i=0; i<m; i++) ans=ans*(n-i)%M;
ans=ans*nfac[m]%M;
return ans;
}
void Work(int node,int Fa)
{
Len[node]=0;
f[node]=g[node]=1;
for (edge *p=head[node]; p; p=p->Next)
{
int son=p->obj;
if (son==Fa) continue;
Son[node].push_back(son);
Work(son,node);
}
if (!Son[node].size())
{
Len[node]=2;
Hash_val[node]=1;
}
else
{
int k=Son[node].size();
sort(Son[node].begin(),Son[node].begin()+k,Comp);
Len[node]=1;
LL &v=Hash_val[node];
v=0;
for (int i=0; i<Son[node].size(); i++)
{
int x=Son[node][i];
v=(v*Two[ Len[x] ]%M+Hash_val[x])%M;
}
v=((v<<1)|1)%M;
int head=0;
while (head<k)
{
int tail=head;
int x=Son[node][head];
while ( tail<k && Judge(x,Son[node][tail]) ) tail++;
int num=tail-head;
f[node]=f[node]*Get(g[x],num)%M;
g[node]=g[node]*Get( (f[x]+g[x])%M ,num)%M;
head=tail;
}
}
}
int main()
{
freopen("3162.in","r",stdin);
freopen("3162.out","w",stdout);
scanf("%d",&n);
for (int i=1; i<=n; i++) head[i]=NULL;
for (int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
Add(x,y);
Add(y,x);
}
dep[1]=1;
Find(1);
int root=1;
for (int i=2; i<=n; i++)
if (dep[i]>dep[root]) root=i;
fa[root]=0;
dep[root]=1;
Find(root);
int bot=1;
for (int i=2; i<=n; i++)
if (dep[i]>dep[bot]) bot=i;
len=0;
while (bot!=root) d[++len]=bot,bot=fa[bot];
d[++len]=root;
nfac[0]=nfac[1]=1;
for (int i=2; i<=n; i++)
{
LL x=M/i,y=M%i;
nfac[i]=M-x*nfac[y]%M;
}
for (int i=1; i<=n; i++) nfac[i]=nfac[i-1]*nfac[i]%M;
Two[0]=1;
for (int i=1; i<=n; i++) Two[i]=(Two[i-1]<<1)%M;
if (len&1)
{
root=d[(len+1)>>1];
Work(root,0);
f[root]=(f[root]+g[root])%M;
printf("%I64d\n",f[root]);
}
else
{
root=d[len>>1];
bot=d[(len>>1)+1];
Work(root,bot);
Work(bot,root);
LL ans;
if ( Judge(bot,root) )
ans=(Get( (f[bot]+g[bot])%M ,2LL)-Get(f[bot],2LL)+M)%M;
else ans=(f[bot]*g[root]%M+g[bot]*f[root]%M+g[bot]*g[root]%M)%M;
printf("%I64d\n",ans);
}
return 0;
}