OSDN Git Service

Update tree-hash.md
authorTrisolaris HD <36555123+TrisolarisHD@users.noreply.github.com>
Sun, 31 Mar 2019 09:43:24 +0000 (17:43 +0800)
committerGitHub <noreply@github.com>
Sun, 31 Mar 2019 09:43:24 +0000 (17:43 +0800)
添加了 Method 1 的代码。
修改了一些细节。

docs/graph/tree-hash.md

index b895ee7..0d4376f 100644 (file)
@@ -1,26 +1,26 @@
 ## 树哈希
 
-我们有时需要判断一些树是否同构。这时,选择恰当的哈希方式将树哈希成数字是一个优秀的方案。
+我们有时需要判断一些树是否同构。这时,选择恰当的哈希方式来将树映射成一个便于储存的哈希值(一般是 32 位或 64 位整数)是一个优秀的方案。
 
-与字符串哈希不同的是,树哈希有很多种哈希方式,下面将选出几种较为常用的介绍。
+树哈希有很多种哈希方式,下面将选出几种较为常用的来加以介绍。
 
 ### Method I
 
 #### Formula
 
 $$
-f_{now}=size_{now} \times \sum f_{son_{now,i}}\times bas^{i-1}
+f_{now}=size_{now} \times \sum f_{son_{now,i}}\times seed^{i-1}
 $$
 
 ##### Notes
 
-其中 $f_x$ 为以节点 $x$ 为根的子树对应的哈希值,特殊地,我们令叶子节点的哈希值为 $1$
+其中 $f_x$ 为以节点 $x$ 为根的子树对应的哈希值。特殊地,我们令叶子节点的哈希值为 $1$ 。
 
 $size_{x}$ 表示以节点 $x$ 为根的子树大小。
 
-$son_{x,i}$ 表示 $x$ 所有子节点按 $f$ 排序后排名为 $i$ 的儿子。
+$son_{x,i}$ 表示 $x$ 所有子节点以 $f$ 作为关键字排序后排名第 $i$ 的儿子。
 
-$bas$ 为选定的一个合适的质数(对字符串 hash 有了解的人一定不陌生)
+$seed$ 为选定的一个合适的种子(最好是质数,对字符串 hash 有了解的人一定不陌生)
 
 上述哈希过程中,可以适当取模避免溢出或加快运行速度。
 
@@ -29,30 +29,30 @@ $bas$ 为选定的一个合适的质数(对字符串 hash 有了解的人一
 #### Formula
 
 $$
-f_{now}=\bigoplus f_{son_{now,i}}\times bas+size_{son_{now,i}}
+f_{now}=\bigoplus f_{son_{now,i}}\times seed+size_{son_{now,i}}
 $$
 
 ##### Notes
 
-其中 $f_x$ 为以节点 $x$ 为根的子树对应的哈希值,特殊地,我们令叶子节点的哈希值为 $1$
+其中 $f_x$ 为以节点 $x$ 为根的子树对应的哈希值。特殊地,我们令叶子节点的哈希值为 $1$ 。
 
 $size_{x}$ 表示以节点 $x$ 为根的子树大小。
 
 $son_{x,i}$ 表示 $x$ 所有子节点之一(不用排序)。
 
-$bas$ 为选定的一个合适的质数
+$seed$ 为选定的一个合适的质数。
 
-$\large\bigoplus$表示异或和
+$\large\bigoplus$表示异或和
 
 ### Example
 
 #### Problem
 
-[Luogu P5403](<https://www.luogu.org/problemnew/show/P5043>)
+[Luogu P5403](https://www.luogu.org/problemnew/show/P5043)
 
 #### Solution
 
-我们用上述方式任选其一进行哈希,注意到我们求得的是子树的hash值,也就是说只有当根一样时同构的两棵子树hash值才相同。由于数据范围较小,我们可以暴力求出以每个点为根时的哈希值,排序后比较。
+我们用上述方式任选其一进行哈希,注意到我们求得的是子树的 hash 值,也就是说只有当根一样时同构的两棵子树 hash 值才相同。由于数据范围较小,我们可以暴力求出以每个点为根时的哈希值,排序后比较。
 
 如果数据范围较大,我们可以通过找重心的方式来优化复杂度。(一棵树的重心最多只有两个,分别比较即可)
 
@@ -60,122 +60,248 @@ $\large\bigoplus$表示异或和
 
 ##### Method I
 
-```
-
-```
-
+??? " 例题参考代码 "
+
+       ```cpp
+       #include <cstdio>
+       #include <algorithm>
+
+       using i64 = long long;
+       using u64 = unsigned long long;
+
+       constexpr int maxT = 50;
+       constexpr int maxn = 50;
+       constexpr int SEED = 98243;
+       constexpr int inf = 2147483647;
+
+       inline int io() {
+               static int _;
+               return scanf("%d", &_), _;
+       }
+
+       template <class _Tp>
+               inline _Tp Max(const _Tp x, const _Tp y) {
+                       return x > y ? x : y;
+               }
+       template <class _Tp>
+               inline void chkMax(_Tp &x, const _Tp y) {
+                       (x < y) && (x = y);
+               }
+       template <class _Tp>
+               inline void chkMin(_Tp &x, const _Tp y) {
+                       (x > y) && (x = y);
+               }
+       template <class _Tp>
+               inline void swap(_Tp &x, _Tp &y) {
+                       _Tp z = x; x = y; y = z;
+               }
+
+       struct Edge {
+               int v; Edge *las;
+               inline Edge *init(const int to, Edge* const ls)
+                       {return v = to, las = ls, this;}
+       } *las[maxn + 1], pool[maxn << 1], *alc = pool - 1;
+
+       inline void lnk(const int u, const int v) {
+               if (u == 0) return;
+               las[u] = (++alc)->init(v, las[u]);
+               las[v] = (++alc)->init(u, las[v]);
+       }
+
+       u64 hval[maxn + 1];
+       void calc(const int u, const int fa) {
+               static u64 lis[maxn + 1];
+               static int sz[maxn + 1];
+
+               /* DFS 时计算 size */
+               sz[u] = 1;
+               for (Edge *o = las[u]; o; o = o->las)
+                       if (o->v != fa)
+                               calc(o->v, u),
+                               sz[u] += sz[o->v];
+
+               /* 将 u 各个儿子的哈希值排序 */
+               int cnt = 0;
+               for (Edge *o = las[u]; o; o = o->las)
+                       if (o->v != fa)
+                               lis[++cnt] = hval[o->v];
+               std::sort(lis + 1, lis + cnt + 1);
+
+               /* 计算 u 的哈希值 */
+               u64 val = 0;
+               for (int i = 1; i <= cnt; ++i)
+                       val = val * SEED + lis[i];
+               hval[u] = val ? val * sz[u] : 1;
+       }
+
+       int sz[maxn + 1],
+               mxsz[maxn + 1];
+       void precalc(const int u, const int fa) {
+               /* 找树的重心 */
+               sz[u] = 1; mxsz[u] = 0;
+               for (Edge *o = las[u]; o; o = o->las)
+                       if (o->v != fa) {
+                               precalc(o->v, u);
+                               sz[u] += sz[o->v];
+                               chkMax(mxsz[u], sz[o->v]);
+                       }
+       }
+
+       int main() {
+               static int n[maxT + 1];
+               static u64 val[maxT + 1][2];
+
+               const int T = io();
+               for (int i = 1; i <= T; ++i) {
+                       n[i] = io();
+                       for (int u = 1; u <= n[i]; ++u)
+                               lnk(io(), u);
+
+                       precalc(1, 0);
+                       int rtsz = inf, cnt = 0;
+                       for (int u = 1; u <= n[i]; ++u)
+                               chkMin(rtsz, Max(mxsz[u], n[i] - sz[u]));
+                       for (int u = 1; u <= n[i]; ++u)
+                               if (rtsz == Max(mxsz[u], n[i] - sz[u]))
+                                       calc(u, 0), val[i][cnt++] = hval[u];
+                                       /* 如果这个点是重心就计算其哈希值 */
+
+                       if (cnt == 2 && val[i][0] > val[i][1])
+                               swap(val[i][0], val[i][1]);
+
+                       /* 清空数组 */
+                       for (int u = 1; u <= n[i]; ++u)
+                               las[u] = nullptr; alc = pool - 1;
+               }
+
+               for (int i = 1; i <= T; ++i) {
+                       bool flag = true;
+                       for (int j = 1; j != i; ++j)
+                               if (n[i] == n[j] && val[i][0] == val[j][0] && val[i][1] == val[j][1]) {
+                                       /* 若树 j 与树 i 点数相同且重心哈希值相同,则其同构 */
+                                       flag = false;
+                                       printf("%d\n", j);
+                                       break;
+                               }
+                       if (flag) printf("%d\n", i);
+               }
+
+               return 0;
+       }
+       ```
 
 
 ##### Method II
 
-```
-#include<cstdio>
-#include<algorithm>
-#include<vector>
-#include<tr1/unordered_map>
-
-class Solution{
-private :
-    typedef unsigned long long ull;
-    typedef std::vector<int>::iterator it;
-    static const ull seed = 2333233233;
-    static const int maxn = 107;
-
-    int n, m, size[maxn], lastRoot, root, lastMax, Max, ans;
-    ull hash[maxn], res;
-    std::vector<int> e[maxn];
-    std::tr1::unordered_map<ull, int> id;
-
-    ull getHash(int now, int fa) {
-        size[now] = 1;
-        hash[now] = 1;
-        for (register it i = e[now].begin(); i != e[now].end(); ++i) {
-            int v = *i;
-            if (v == fa) {
-                continue;
-            }
-            hash[now] ^= getHash(v, now) * seed + size[v];
-            size[now] += size[v];
-        }
-        return hash[now];
-    }
-
-    void getRoot(int now, int fa) {
-        int max = 0;
-        size[now] = 1;
-        for (register it i = e[now].begin(); i != e[now].end(); ++i) {
-            int v = *i;
-            if (v == fa) {
-                continue;
-            }
-            getRoot(v, now);
-            size[now] += size[v];
-            max = std::max(max, size[v]);
-        }
-        max = std::max(max, n - size[now]);
-        if (max < Max && now != lastRoot) {
-            root = now;
-            Max = max;
-        }
-    }
-    
-public :
-    Solution() {
-        get();
-        solve();
-    }
-
-    void get() {
-        scanf("%d", &m);
-        for (register int i = 1; i <= m; i++) {
-            scanf("%d", &n);
-            for (register int j = 1; j <= n; j++) {
-                std::vector<int>().swap(e[j]);
-            }
-            for (register int j = 1, fa; j <= n; j++) {
-                scanf("%d", &fa);
-                if (!fa) {
-                    root = j;
-                } else {
-                    e[fa].push_back(j);
-                    e[j].push_back(fa);
-                }
-            }
-            lastRoot = root = 0;
-            Max = n;
-            getRoot(1, 0);
-            lastRoot = root, lastMax = Max;
-            res = getHash(root, 0);
-            if (!id.count(res)) {
-                id[res] = i;
-            }
-            ans = id[res];
-
-            Max = n;
-            getRoot(1, 0);
-            if (lastMax == Max) {
-                res = getHash(root, 0);
-                if (!id.count(res)) {
-                    id[res] = i;
-                }
-                ans = std::min(ans, id[res]);
-            }
-            printf("%d\n", ans);
-        }
-    }
-
-    void solve() {
-        
-    }
-};
-Solution sol;
-
-int main() {}
-
-```
-
+??? " 例题参考代码 "
+
+       ```cpp
+       #include<cstdio>
+       #include<vector>
+       #include<algorithm>
+       #include<tr1/unordered_map>
+
+       class Solution{
+       private :
+               typedef unsigned long long ull;
+               typedef std::vector<int>::iterator it;
+               static const ull seed = 2333233233;
+               static const int maxn = 107;
+
+               int n, m, size[maxn], lastRoot, root, lastMax, Max, ans;
+               ull hashval[maxn], res;
+               std::vector<int> e[maxn];
+               std::tr1::unordered_map<ull, int> id;
+
+               ull getHash(int now, int fa) {
+                       size[now] = 1;
+                       hashval[now] = 1;
+                       for (register it i = e[now].begin(); i != e[now].end(); ++i) {
+                               int v = *i;
+                               if (v == fa) {
+                                       continue;
+                               }
+                               hashval[now] ^= getHash(v, now) * seed + size[v];
+                               size[now] += size[v];
+                       }
+                       return hashval[now];
+               }
+
+               void getRoot(int now, int fa) {
+                       int max = 0;
+                       size[now] = 1;
+                       for (register it i = e[now].begin(); i != e[now].end(); ++i) {
+                               int v = *i;
+                               if (v == fa) {
+                                       continue;
+                               }
+                               getRoot(v, now);
+                               size[now] += size[v];
+                               max = std::max(max, size[v]);
+                       }
+                       max = std::max(max, n - size[now]);
+                       if (max < Max && now != lastRoot) {
+                               root = now;
+                               Max = max;
+                       }
+               }
+               
+       public :
+               Solution() {
+                       get();
+                       solve();
+               }
+
+               void get() {
+                       scanf("%d", &m);
+                       for (register int i = 1; i <= m; i++) {
+                               scanf("%d", &n);
+                               for (register int j = 1; j <= n; j++) {
+                                       std::vector<int>().swap(e[j]);
+                               }
+                               for (register int j = 1, fa; j <= n; j++) {
+                                       scanf("%d", &fa);
+                                       if (!fa) {
+                                               root = j;
+                                       } else {
+                                               e[fa].push_back(j);
+                                               e[j].push_back(fa);
+                                       }
+                               }
+                               lastRoot = root = 0;
+                               Max = n;
+                               getRoot(1, 0);
+                               lastRoot = root, lastMax = Max;
+                               res = getHash(root, 0);
+                               if (!id.count(res)) {
+                                       id[res] = i;
+                               }
+                               ans = id[res];
+
+                               Max = n;
+                               getRoot(1, 0);
+                               if (lastMax == Max) {
+                                       res = getHash(root, 0);
+                                       if (!id.count(res)) {
+                                               id[res] = i;
+                                       }
+                                       ans = std::min(ans, id[res]);
+                               }
+                               printf("%d\n", ans);
+                       }
+               }
+
+               void solve() {
+                       
+               }
+       };
+       Solution sol;
+
+       int main() {}
+
+       ```
 
 
 ### At Last
 
-äº\8bå®\9eä¸\8aï¼\8cæ \91å\93\88å¸\8cæ\98¯å¾\88ç\81µæ´»ç\9a\84ã\80\82å\8f¯ä»¥æ\9c\89å\90\84ç§\8då\90\84æ ·å¥\87æ\80ªç\9a\84姿å\8a¿æ\9d¥è¿\9bè¡\8c hashï¼\8cå\8fªé\9c\80注æ\84\8f充分性与必要性,选手完全可以设计出与上述方式不同的 hash 方式。
+äº\8bå®\9eä¸\8aï¼\8cæ \91å\93\88å¸\8cæ\98¯å\8f¯ä»¥å¾\88ç\81µæ´»ç\9a\84ï¼\8cå\8f¯ä»¥æ\9c\89å\90\84ç§\8då\90\84æ ·å¥\87æ\80ªç\9a\84姿å\8a¿æ\9d¥è¿\9bè¡\8c hashï¼\8cå\8fªé\9c\80ä¿\9dè¯\81充分性与必要性,选手完全可以设计出与上述方式不同的 hash 方式。