树哈希(Tree Hash)

树哈希(Tree Hash)

顾名思义,对树进行哈希,经常判断两个树是否同构。一下均为对有根树的算法,而无根树只需要找重心

无权树

方法一

f n o w = s i z e n o w × ∑ f s o n ( n o w , i ) × s e e d i − 1 f_{now}=size_{now} \times \sum f_{son(now,i)}\times seed^{i-1} fnow=sizenow×fson(now,i)×seedi1

其中 f x f_x fx 为以节点 x x x 为根的子树对应的哈希值。特殊地,我们令叶子节点的哈希值为 1 1 1

s i z e x size_{x} sizex 表示以节点 x x x 为根的子树大小。

s o n x , i son_{x,i} sonx,i 表示 x x x 所有子节点以 f f f 作为关键字排序后排名第 i i i 的儿子。

s e e d seed seed 为选定的一个合适的种子(最好是质数,对字符串 hash 有了解的人一定不陌生)

上述哈希过程中,可以适当取模避免溢出或加快运行速度。

方法二

f n o w = ⨁ f s o n ( n o w , i ) × s e e d + s i z e s o n ( n o w , i ) f_{now}=\bigoplus f_{son(now,i)}\times seed+size_{son(now,i)} fnow=fson(now,i)×seed+sizeson(now,i)

其中 f x f_x fx 为以节点 x x x 为根的子树对应的哈希值。特殊地,我们令叶子节点的哈希值为 1 1 1

s i z e x size_{x} sizex 表示以节点 x x x 为根的子树大小。

s o n x , i son_{x,i} sonx,i 表示 x x x 所有子节点之一(不用排序)。

s e e d seed seed 为选定的一个合适的质数。

⨁ \bigoplus 表示异或和。

方法三

f n o w = 1 + ∑ f s o n ( n o w , i ) × p r i m e ( s i z e s o n ( n o w , i ) ) f_{now}=1+\sum f_{son(now,i)} \times prime(size_{son(now,i)}) fnow=1+fson(now,i)×prime(sizeson(now,i))

其中 f x f_x fx 为以节点 x x x 为根的子树对应的哈希值。

s i z e x size_{x} sizex 表示以节点 x x x 为根的子树大小。

s o n x , i son_{x,i} sonx,i 表示 x x x 所有子节点之一(不用排序)。

p r i m e ( i ) prime(i) prime(i) 表示第 i i i 个质数。

P5043 树同构

#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

using namespace std;

typedef long long ll;

struct Edge
{
    int to;
    int nxt;
} e[500];

int head[100];
int siz[100];
ll has[100][100];
int tot = 0;

ll base = 233;
ll mod = 1000000007;

void add(int u, int v)
{
    tot++;
    e[tot].to = v;
    e[tot].nxt = head[u];
    head[u] = tot;
}

ll hashTree(int u, int r)
{
    siz[u] = 1;
    vector<ll> subhash;
    for (int ne = head[u]; ne; ne = e[ne].nxt)
    {
        int to = e[ne].to;
        if (to == r)
            continue;
        subhash.push_back(hashTree(to, u));
        siz[u] += siz[to];
    }
    if (siz[u] == 1)
        return 1;
    sort(subhash.begin(), subhash.end());

    ll ans = 0;
    for (int i = 0; i < subhash.size(); i++)
    {
        ans = (ans * base + subhash[i]) % mod;
    }

    ans *= siz[u];

    return ans;
}

int main()
{
    int M;
    cin >> M;
    for (int i = 1; i <= M; i++)
    {
        tot = 0;
        memset(head, 0, sizeof(head));
        int n;
        cin >> n;
        int root = 0;
        for (int j = 1; j <= n; j++)
        {
            int fa;
            cin >> fa;
            if (fa == 0)
            {
                root = i;
            }
            else
            {
                add(fa, j);
                add(j, fa);
            }
        }
        for (int j = 1; j <= n; j++)
        {
            has[i][j] = hashTree(j, 0);
        }
        for (int j = 1; j <= M; j++)
        {
            for (int r1 = 1; r1 <= n; r1++)
            {
                for (int r2 = 1; r2 <= n; r2++)
                {
                    if (has[i][r1] == has[j][r2])
                    {
                        printf("%d\n", j);
                        goto gg;
                    }
                }
            }
        }
    gg:
        continue;
    }

    return 0;
}

有权树

只需要将权值加入进公式即可:

f n o w = s i z e n o w × ∑ f s o n ( n o w , i ) × s e e d i − 1 × w n o w f_{now}=size_{now} \times \sum f_{son(now,i)}\times seed^{i-1} \times w_{now} fnow=sizenow×fson(now,i)×seedi1×wnow

另外,我们可以使用字符串hash的方式:

例如题目:LeetCode 1948

我们定义一个叶子节点的序列化字符串为空字符串,一个非叶子节点的字符串序列化为:

f n o w = H a s h ( ∑ w s o n ( f s o n ) ) f_{now} = Hash(\sum w_{son}(f_{son})) fnow=Hash(wson(fson))

其中求和表示拼接字符串, H a s h Hash Hash为某一个字符串哈希函数。此时拼接顺序一定要根据权值进行排序。

如果是无权树,那么只剩下括号序列,就是树的最小括号表示法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值