刷题总结——树的同构(bzoj4337 树上hash)
Description
树是一种很常见的数据结构。
我们把N个点,N-1条边的连通无向图称为树。
若将某个点作为根,从根开始遍历,则其它的点都有一个前驱,这个树就成为有根树。
对于两个树T1和T2,如果能够把树T1的所有点重新标号,使得树T1和树T2完全相
同,那么这两个树是同构的。也就是说,它们具有相同的形态。
现在,给你M个有根树,请你把它们按同构关系分成若干个等价类。
Input
第一行,一个整数M。
接下来M行,每行包含若干个整数,表示一个树。第一个整数N表示点数。接下来N
个整数,依次表示编号为1到N的每个点的父亲结点的编号。根节点父亲结点编号为0。
Output
输出M行,每行一个整数,表示与每个树同构的树的最小编号。
Sample Input
4
4 0 1 1 2
4 2 0 2 3
4 0 1 1 1
4 0 1 2 3
Sample Output
1
1
3
1
HINT
【样例解释】
编号为1, 2, 4 的树是同构的。编号为3 的树只与它自身同构。
100% 的数据中,1 ≤ N, M ≤ 50。
题解:
通过树上hash解决树的同构问题的模板题···
先说如何进行树上hash,设f[i]为以i为节点的hash值,则
f[i]=sigma(f[j]*prime[j]) j为son[i]
其中prime为预处理出来的素数表···注意f[j]需要进行排序····
然后对于两颗待判定的树,将两颗树分别以树上每一个节点为根节点求hash值··将根节点的hash值储存起来排序然后两颗树一一比对··如果完全一样则两棵树就一样
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<cstring> #include<string> #include<algorithm> using namespace std; const int N=55; int hash[N][N],n,que[N]; int tot,fst[N],nxt[N*2],go[N*2],f[N]; int prime[]={0,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317}; inline void comb(int a,int b) { nxt[++tot]=fst[a],fst[a]=tot,go[tot]=b; nxt[++tot]=fst[b],fst[b]=tot,go[tot]=a; } inline void dfs(int u,int fa) { unsigned int st[N];int tot=0; st[++tot]=1; for(int e=fst[u];e;e=nxt[e]) { int v=go[e];if(v==fa) continue; dfs(v,u); st[++tot]=f[v]; } f[u]=0;sort(st+1,st+tot+1); for(int i=1;i<=tot;i++) f[u]+=st[i]*prime[i]; } int main() { // freopen("a.in","r",stdin); scanf("%d",&n);int a,b; for(int i=1;i<=n;i++) { scanf("%d",&a); memset(fst,0,sizeof(fst));tot=0; for(int j=1;j<=a;j++) { scanf("%d",&b); if(b) comb(j,b); } for(int j=1;j<=a;j++) { dfs(j,0);hash[i][j]=f[j]; } sort(hash[i]+1,hash[i]+a+1); for(int j=1;j<=i;j++) { bool flag=true; for(int k=1;k<=a;k++) if(hash[j][k]!=hash[i][k]) { flag=false;break; } if(flag==true) { printf("%d\n",j); break; } } } return 0; }