题目
求形态相同的两个最大k-子树。k-子树定义为从u节点到其子树中距离不超过k的所有节点。
题解
树hash,因为我们要从树中选一部分来当k-子树,所以考虑用欧拉序来hash。
用欧拉序的话,如果我们要删一部分,距离超过k的节点的子树时,只需要删in[v]->ou[v]的hash就行了
(但我们发现如果in[v],ou[v]分别用不同的数字表示即可表现树,因为这样的括号序列对应的树的形态是唯一的)
0.预处理欧拉序的hash表
1.先二分答案
2.考虑验证,将深度超过mid+1的节点v加到它往上mid+1的点u的vector中,表示求u的mid-子树时要删去v的子树
3.求出每个点u的mid-子树的hash值,并用unordered_map判重
#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define LL unsigned long long
using namespace std;
using namespace tr1;
const int N=1e5+10,M=1e5+10;
const LL p=31;
int n,m;
int head[N],nex[M],to[M],tot;
void build(int u,int v){tot++;nex[tot]=head[u];to[tot]=v;head[u]=tot;}
//
LL h[N*2],hs[N*2];
LL get(int l,int r){return hs[r]-hs[l-1]*h[r-l+1];}
LL hb(LL h1,LL h2,int len){return h1*h[len]+h2;}
//
int z[N*2],z_p;
int dep[N],maxx[N];
int in[N],ou[N],cnt;
void dfs(int u,int f)
{
dep[u]=dep[f]+1;
in[u]=++cnt;z[cnt]=31;
for(int i=head[u];i;i=nex[i])
{
int v=to[i];dfs(v,u);
maxx[u]=max(maxx[u],maxx[v]+1);
}
ou[u]=++cnt;z[cnt]=13;
}
unordered_map<LL,LL>mp;
vector<LL>son[N];
void dfs2(int u,int k)
{
z[++z_p]=u;
if(dep[u]>k+1)son[z[dep[u]-k-1]].push_back(u);
for(int i=head[u];i;i=nex[i])
{
int v=to[i];dfs2(v,k);
}
z_p--;
}
void init()
{
mp.clear();
for(int i=1;i<=n;i++)son[i].clear();
}
bool check(int k)
{
init();
dfs2(1,k);
for(int i=1;i<=n;i++)
{
if(maxx[i]>=k)//注意,假如u的k-子树中不存在距离为k的点,则u的k-子树是不存在的。
{
LL tmp=0;
int l=in[i],r;
for(int j=0;j<son[i].size();j++)
{
r=in[son[i][j]]-1;
tmp=hb(tmp,get(l,r),r-l+1);
l=ou[son[i][j]]+1;
}tmp=hb(tmp,get(l,ou[i]),ou[i]-l+1);
if(mp.count(tmp))return 1;
mp[tmp]=1;
}
}return 0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&m);
for(int fw,j=1;j<=m;j++)
scanf("%d",&fw),build(i,fw);
}
dfs(1,0);
h[0]=1;
for(int i=1;i<=cnt;i++)h[i]=h[i-1]*p;
for(int i=1;i<=cnt;i++)hs[i]=hs[i-1]*p+z[i];
int l=0,r=n,mid,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d",ans);
}