【BZOJ4754】独特的树叶(JSOI2016)-树同构:树上哈希
测试地址:独特的树叶
做法:本题需要用到树同构:树上哈希。
问题的关键是如何快速判断两棵树同构。要想到一个确定的算法是很难的,因此我们考虑哈希。
考虑这样一种哈希方法:类似树形DP,对于每个点,先递归求解它的儿子的子树,然后把所有儿子按哈希值排序,然后在这个序列末尾加上一个子树大小的数值,然后把这个序列按字符串哈希的方式求出哈希值,这就是当前子树的哈希值。这样我们可以求出以某个点为根的哈希值。于是我们如果我们求出一棵树中所有点为根的哈希值,存在map里,再用另一棵树某一个点的哈希值去map里找,就可以判断两棵树是否同构了。
然而暴力从每个点向下求哈希值是的,无法通过。我们可以借助树形DP中较为常用的思路:换根。即先算出以某一个点为根的哈希信息,然后再进行一次自顶向下的搜索,尝试用以父亲为根的哈希信息算出以当前节点为根的哈希信息。事实上是可行的,详见代码。于是我们就可以做到的复杂度了。
那么对于这一题,我们只需对树的每个叶子节点,看以它为根时,它的儿子传递给它的哈希值,就是去掉这个叶子后树以这个儿子为根的哈希值,于是我们就解决了这一题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const ll P=19260817;
int n,first[100010],tot,siz[100010],deg[100010];
ll pwr[100010],down[100010],Hash[100010];
vector<ll> son[100010],pre[100010];
map<ll,bool> vis;
struct edge
{
int v,next;
}e[200010];
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
void dfs1(int v,int fa)
{
siz[v]=1;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa)
{
dfs1(e[i].v,v);
son[v].push_back(down[e[i].v]);
siz[v]+=siz[e[i].v];
}
sort(son[v].begin(),son[v].end());
down[v]=0;
for(int i=0;i<son[v].size();i++)
{
down[v]=down[v]*P+son[v][i];
pre[v].push_back(down[v]);
}
down[v]=down[v]*P+(ll)siz[v];
}
void dfs2(int v,int fa)
{
deg[v]=1;
if (fa)
{
int s=son[fa].size(),l=0,r=s-1;
while(l<r)
{
int mid=(l+r)>>1;
if (down[v]>son[fa][mid]) l=mid+1;
else r=mid;
}
ll up=0;
if (l>0) up=pre[fa][l-1]*pwr[s-l-1];
up+=pre[fa][s-1]-pre[fa][l]*pwr[s-l-1];
up=up*P+(ll)(n-siz[v]);
son[v].push_back(up);
sort(son[v].begin(),son[v].end());
Hash[v]=0;
pre[v].clear();
for(int i=0;i<son[v].size();i++)
{
Hash[v]=Hash[v]*P+son[v][i];
pre[v].push_back(Hash[v]);
}
Hash[v]=Hash[v]*P+(ll)n;
}
else Hash[v]=down[v],deg[v]=0;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa) deg[v]++,dfs2(e[i].v,v);
}
int main()
{
scanf("%d",&n);
pwr[0]=1;
for(int i=1;i<=n;i++)
pwr[i]=pwr[i-1]*P;
memset(first,0,sizeof(first));
tot=0;
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b),insert(b,a);
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=n;i++)
vis[Hash[i]]=1;
memset(first,0,sizeof(first));
tot=0;
n=n+1;
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b),insert(b,a);
son[i].clear();
pre[i].clear();
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=n;i++)
if (deg[i]==1&&vis[son[i][0]])
{
printf("%d",i);
break;
}
return 0;
}