树hash - 树的同构

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+ysonxfy×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=gfafxprime(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中查。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页