P5043 【模板】树同构([BJOI2015]树的同构)
f
x
=
1
+
∑
y
∈
s
o
n
x
f
y
×
p
r
i
m
e
(
s
i
z
e
y
)
f_x = 1 + \sum_{y\in son_x}{f_y \times prime(size_y)}
fx=1+y∈sonx∑fy×prime(sizey)
g
x
=
g
f
a
−
f
x
∗
p
r
i
m
e
(
s
i
z
e
x
)
+
f
x
g_x = g_{fa} - f_x * prime(size_x) + f_x
gx=gfa−fx∗prime(sizex)+fx
其中:
f
x
f_x
fx 表示
x
x
x 为根的子树的
H
a
s
h
Hash
Hash 值
g
x
g_x
gx 表示以
x
x
x 为根的整棵树的
H
a
s
h
Hash
Hash 值
s
o
n
x
son_x
sonx 表示
x
x
x 的儿子结点集合
s
i
z
e
y
size_y
sizey 表示
y
y
y 为根的子树规模
p
r
i
m
e
(
i
)
prime(i)
prime(i) 表示第
i
i
i 个素数
注意到我们求得的是子树的Hash值,也就是说只有当根一样时同构的两棵子树 hash 值才相同。如果数据范围较小,我们可以暴力求出以每个点为根时的Hash值,也可以通过up and down树形dp的方式,遍历树两遍求出以每个点为根时的Hash值,排序后比较。
如果数据范围较大,我们可以通过找重心的方式来优化复杂度。(一棵树的重心最多只有两个,分别比较即可)
判断无根树同构,通过两遍dfs树形dp,求出每个点为根时的Hash值,排序后比较即可。
时间复杂度: O ( n m ) O(nm) O(nm)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 5007, M = 100007, INF = 0x3f3f3f3f, mod = 998244353;
using namespace std;
int turn;
int n, m;
int head[N], ver[N], nex[M], edge[M], tot;
int f[N], g[N];
vector<int>Hash[N];
int primes[M];
bool vis[M];
int siz[N];
int cnt;
int ans[N];
void add(int x, int y)
{
ver[tot] = y;
nex[tot] = head[x];
head[x] = tot ++ ;
}
void get_primes(int n)
{
for(int i = 2; i <= n; ++ i){
if(vis[i] == 0)
primes[++ cnt] = i;
for(int j = 1; j <= cnt && primes[j] * i <= n; ++ j){
vis[i * primes[j]] = true;
if(i % primes[j] == 0)break;
}
}
}
void init(int x)
{
memset(head, -1, sizeof head);
tot = 0;
Hash[x].clear();
}
void dfs1(int x, int fa)
{
siz[x] = f[x] = 1;
for(int i = head[x]; ~i; i = nex[i]){
int y = ver[i];
if(y == fa)continue;
dfs1(y, x);
f[x] = (f[x] + 1ll * f[y] * primes[siz[y]] % mod) % mod;
siz[x] += siz[y];
}
}
void dfs2(int x, int fa, int fa_f)
{
g[x] = (f[x] + 1ll * fa_f * primes[n - siz[x]] % mod) % mod;
Hash[turn].push_back(g[x]);
fa_f = (1ll * fa_f * primes[n - siz[x]] % mod + 1) % mod;
for(int i = head[x]; ~i; i = nex[i]){
int y = ver[i];
if(y == fa)continue;
dfs2(y, x, (1ll * fa_f + f[x] - 1 - 1ll * f[y] * primes[siz[y]] % mod + mod) % mod);
}
}
bool Equal(int x, int y)
{
if(Hash[x].size() != Hash[y].size())return false;
for(int i = 0; i < Hash[x].size(); ++ i){
if(Hash[x][i] != Hash[y][i])
return false;
}
return true;
}
int main()
{
get_primes(M - 1);
scanf("%d", &m);
for(turn = 1; turn <= m; ++ turn){
scanf("%d", &n);
init(turn);
for(int j = 1; j <= n; ++ j){
int x;
scanf("%d", &x);
if(x)add(x, j), add(j, x);
}
dfs1(1, 0);//处理siz,f;
dfs2(1, 0, 0);//处理g
//for(int j = 1; j <= n; ++ j)
// Hash[i].push_back(g[i]);
sort(Hash[turn].begin(), Hash[turn].end());
}
//puts("1");
for(int i = 1; i <= m; ++ i)
ans[i] = i;
for(int i = 2; i <= m; ++ i){
for(int j = 1; j < i; ++ j){
if(Equal(i, j)){
ans[i] = ans[j];
break;
}
}
}
for(int i = 1; i <= m; ++i){
printf("%d\n", ans[i]);
}
return 0;
}
JSOI2016 独特的树叶
对第一棵树的所有点为根的hash值建立set,然后枚举第二棵树,在set中查。