题目传送门: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 个数的组合方案为 Cmn+m−1 C n + m − 1 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
#include
#include
#include
#include
#include
#include
#include
#include
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; ireturn 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; iint x=Son[node][i];
v=(v*Two[ Len[x] ]%M+Hash_val[x])%M;
}
v=((v<<1)|1)%M;
int head=0;
while (headint tail=head;
int x=Son[node][head];
while ( tailint 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; iint 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;
}