【树hash】「2017 山东一轮集训 Day3」第二题

题目

求形态相同的两个最大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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值