Gushing over Pr|

BigSmall_En

园龄:3年2个月粉丝:3关注:5

2022-10-04 20:33阅读: 40评论: 0推荐: 0

树哈希小记

树哈希

感觉没什么好说的,类似树形 DP 一样求出一个点的子树的哈希值,用以判断树是否同构,哈希函数很多,这里给出一种实现。

UOJ板子

const int N=1000006;
int n,siz[N],ans;
vector<int>edge[N];
map<ull,bool>vis;
ull hsh[N];
ull poland(ull x){
	x^=x>>7;x^=x<<13;x^=x>>17;
	return x;
}
void dfsp(int u,int f){
	hsh[u]=1;siz[u]=1;
	for(auto v:edge[u]){
		if(v==f)continue;
		dfsp(v,u);
		siz[u]+=siz[v];
		hsh[u]+=poland(hsh[v]);
	}
	hsh[u]*=siz[u];
	if(vis.find(hsh[u])==vis.end())
		{++ans;vis[hsh[u]]=1;}
}
int main(){
	int n=read();
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	dfsp(1,0);
	printf("%d\n",ans);
	return 0;
}

例题

LG5043 【模板】树同构([BJOI2015]树的同构)

无根树树同构,以每个点为根求一边树哈希判断即可。

LG4323 [JSOI2016]独特的树叶

JYY有两棵树 \(A\)\(B\) :树 \(A\)\(N\) 个点,编号为 \(1\)\(N\) ;树 \(B\)\(N+1\) 个节点,编号为 \(1\)\(N+1\)

JYY 知道树 \(B\) 恰好是由树 \(A\) 加上一个叶节点,然后将节点的编号打乱后得到的。他想知道,这个多余的叶子到底是树 \(B\) 中的哪一个叶节点呢?

求出 \(A\) 树以每个节点为根的哈希值,和 \(B\) 树去掉一个叶子节点的哈希值,如果这两棵树中有哈希值相等,则可以得出对应的叶子节点。

直接做是 \(O(n^2)\) 的,由于树哈希的过程其实就是树形 DP,所以实际上可以换根做到 \(O(n)\)

这里的哈希函数是,其中 \(S\) 是常数。

\[dp_u\gets dp_u\oplus (dp_v\times S+siz_v) \]

然后用 set 就可以对比了。

具体见代码吧

typedef long long ull;
const ull S=114514;
struct Edge{
	int enxt[M],head[N],to[M],ent,n,deg[N];
	ull dp[N],siz[N],has[N];
	inline void addline(int u,int v){
		to[++ent]=v;
		enxt[ent]=head[u];
		head[u]=ent;
		++deg[v];
	}
	inline void addedge(int u,int v){
		addline(u,v);
		addline(v,u);
	}
	inline void clear(){
		memset(head,0,sizeof(head));
		ent=0;
	}
	inline void build(){
		for(int i=1;i<n;++i){
			int u=read(),v=read();
			addedge(u,v);
		}
	}
	inline void dfsp(int u,int f){
		dp[u]=siz[u]=1ull;
		for(int i=head[u];i;i=enxt[i]){
			int v=to[i];
			if(v==f)continue;
			dfsp(v,u);
			siz[u]+=siz[v];
			dp[u]^=(dp[v]*S+siz[v]);
		}
	}
	inline void dfsg(int u,int f){//换根dp
		if(!f)has[u]=dp[u];
		else has[u]=dp[u]^((has[f]^(dp[u]*S+siz[u]))*S+n-siz[u]);
		for(int i=head[u];i;i=enxt[i]){
			int v=to[i];
			if(v==f)continue;
			dfsg(v,u);
		}
	}
}e1,e2;

int n,siz[N];
set<ull>se;

int main(){
	n=read();
	e1.n=n;e1.build();
	e1.dfsp(1,0);e1.dfsg(1,0);
	//e1.debug();
	for(int i=1;i<=n;++i)
		se.insert(e1.has[i]);

	e2.n=n+1;e2.build();
	/*for(int i=1;i<=e2.n;++i){
		if(e2.deg[i]!=1){//注意从第二颗树的非叶子节点开始dp,这样可以才能保证下面计算的正确性
			e2.dfsp(i,0);e2.dfsg(i,0);
			break;
		}
	}*/
	e2.dfsp(1,0);e2.dfsg(1,0);
	for(int i=1;i<=e2.n;++i){
		if(e2.deg[i]==1){
			if(se.count(e2.has[e2.to[e2.head[i]]]^(e2.dp[i]*S+1)))
				return printf("%d\n",i),0;
		}
	}
	return 0;
}
// 179ms /  12.19MB /  1.91KB C++14 (GCC 9) O2

CF718D Andrew and Chemistry

给你一个有 \(n\) 个点的树。当每一个点的度不超过 \(4\) 时这棵树是合法的。现在让你再添加一个点,在树仍然合法的情况下,一共有多少种树。

当两棵树同构时视作同一种。

根据 LG4323 的思路,我们可以使用换根 DP 求出以每个点为根的哈希值,然后对度数小于 \(4\) 的点求不同种的哈希值数量即可。

但是似乎没有用到度数为 \(4\) 这一性质。

使用记搜,如果当前节点已经访问过就直接返回原来的值。

至于哈希的过程,我们考虑用 \(\text{vector}\) 来存储。

每个 \(\text{vector}\) 所存储的都是其儿子的哈希值,就可以不断递归向上合并。

见代码吧。

const int N=100005,M=200005;
map<int,int>dp[N];
map<vector<int>,int>vis;int tot;
set<int>se;

struct Edge{
	int enxt[M],head[N],to[M],ent,n,deg[N];
	inline void addline(int u,int v){
		to[++ent]=v;
		enxt[ent]=head[u];
		head[u]=ent;
		++deg[v];
	}
	inline void addedge(int u,int v){
		addline(u,v);
		addline(v,u);
	}
	inline void clear(){
		memset(head,0,sizeof(head));
		ent=0;
	}
	inline void build(){
		for(int i=1;i<n;++i){
			int u=read(),v=read();
			addedge(u,v);
		}
	}
	int dfs(int u,int f){
		if(dp[u].find(f)!=dp[u].end())return dp[u][f];//因为每个点的度数<=4,则总的状态数小于 n*4*2,其中2代表不同方向
		vector<int>tmp;
		for(int i=head[u];i;i=enxt[i]){
			int v=to[i];
			if(v==f)continue;
			tmp.push_back(dfs(v,u));
		}
		sort(tmp.begin(),tmp.end());
		if(vis.find(tmp)==vis.end())vis[tmp]=++tot;
		return dp[u][f]=vis[tmp];
	}
	void solve(){
		for(int i=1;i<=n;++i){
			if(deg[i]<4)se.insert(dfs(i,0));
			//printf("%d %d\n",deg[i],dfs(i,0));
		}
	}
}e1;
int n;

int main(){
	e1.n=n=read();
	e1.build();e1.solve();
	printf("%d\n",(int)se.size());
	return 0;
}
//orz
//本质是求以不同的点为根进行树哈希得到的不同形态数,因为这题状态比较少,故可以采取上方这种很“暴力”的方法,换根也是可以的。
//可能换根写起来也没复杂多少,但是这种方法挺有新意的。

本文作者:BigSmall_En

本文链接:https://www.cnblogs.com/BigSmall-En/p/16754418.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   BigSmall_En  阅读(40)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起