树的同构模板题(法1.最小表示法+法2.树哈希)
樹的同構
- problem
- solution
- code
- solution
- code
problem
模板題
solution
Ⅰ. 最小表示法
將樹轉化為 0/10/10/1 括號序列:從根開始 dfs\text{dfs}dfs,000 就往下遍歷一個兒子,111 就返回,構成一個 2×n2\times n2×n 的括號序列。
顯然,括號序列與樹的形態是唯一對應的。
- 有根樹
- 若兒子遍歷順序是固定的。顯然括號序列只有唯一一種。
- 若兒子遍歷順序不固定。就會有多種合法的括號序列,不妨欽定字典序最小的為這棵樹的括號序列,這個特殊的括號序列有單獨的名稱:最小表示。
顯然,兩棵有根樹的最小表示相同是同構的充要條件。
具體實現:遞歸地構造,對于點 uuu,先把兒子 vvv 的括號序列都處理出來后,按照兒子的字典序從小到大排序,然后順次接起來。在這個拼接的括號序列外面用 000(進入 uuu 子樹求解)111(完成 uuu 子樹內的遍歷) “包起來”就表示把 uuu 子樹遍歷完然后返回上一層的最小表示了。
時間復雜度為 O(n2)O(n^2)O(n2)。最壞情況為鏈。
因為括號序列肯定是用字符串 string\text{string}string 類型儲存。
那么 f[u]+=f[v]f[u]+=f[v]f[u]+=f[v] 這一句話的操作復雜度其實是 O(len)O(len)O(len) 的。
- 無根樹
比較暴力的想法就是對于每個點都當作根,做一遍 dfs\text{dfs}dfs。時間復雜度為 O(n3)O(n^3)O(n3)。
對于本題而言,又有 mmm 棵樹,時間復雜度就是 O(n3m)O(n^3m)O(n3m) 的。
實際上,最小表示法就是為了定義一個規則,讓一棵樹擁有唯一的括號序列。如果喜歡最大表示法也是可以的。
所以可以對無根樹找一個特殊的規則,來區別不同構的樹。
這里我們選擇重心為根時的括號序列當作無根樹的括號序列代表。
兩個重心的話就取字典序較小的。
時間復雜度就降為 O(n2m)O(n^2m)O(n2m)。
code
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 55 int n, m, Max, cnt; string Min; struct node { int to, nxt; }E[maxn << 1]; int head[maxn], siz[maxn], MaxSiz[maxn]; string f[maxn], g[maxn], mp[maxn];void init() {memset( head, -1, sizeof( head ) );memset( MaxSiz, 0, sizeof( MaxSiz ) );cnt = 0; Max = n; Min = "1"; }void addedge( int u, int v ) {E[cnt] = { v, head[u] };head[u] = cnt ++; }void dfs1( int u, int fa ) {siz[u] = 1;for( int i = head[u];~ i;i = E[i].nxt ) {int v = E[i].to;if( v == fa ) continue;dfs1( v, u );siz[u] += siz[v];MaxSiz[u] = max( MaxSiz[u], siz[v] );}MaxSiz[u] = max( MaxSiz[u], n - siz[u] );Max = min( Max, MaxSiz[u] ); }void dfs2( int u, int fa ) {f[u] = "0";for( int i = head[u];~ i;i = E[i].nxt )if( E[i].to ^ fa ) dfs2( E[i].to, u );int tot = 0;for( int i = head[u];~ i;i = E[i].nxt )if( E[i].to ^ fa ) g[++ tot] = f[E[i].to];sort( g + 1, g + tot + 1 );for( int i = 1;i <= tot;i ++ ) f[u] += g[i];f[u] += "1"; }int main() {scanf( "%d", &m );for( int j = 1;j <= m;j ++ ) {scanf( "%d", &n );init();for( int i = 1, x;i <= n;i ++ ) {scanf( "%d", &x );if( x ) addedge( x, i ), addedge( i, x );}dfs1( 1, 0 );for( int i = 1;i <= n;i ++ )if( MaxSiz[i] == Max ) {dfs2( i, 0 );Min = min( Min, f[i] );}mp[j] = Min;for( int i = 1;i <= j;i ++ )if( mp[i] == mp[j] ) {printf( "%d\n", i );break;}}return 0; }solution
Ⅱ . 樹哈希
多項式哈希,即 [1,r][1,r][1,r] 的哈希值減去 r?l+1r-l+1r?l+1 乘上 [1,l][1,l][1,l] 的哈希值。
同理,有根樹且兒子有順序,哈希就是一一對應,正確的。
如果有根樹且兒子沒順序,就按照兒子的哈希值排序后,再哈希。
有根樹擴展到無根樹也是尋找重心。
得出哈希值后暴力比較即可。
時間復雜度為 O(nmlog?n)O(nm\log n)O(nmlogn),瓶頸在于排序。(如果你不喜歡 log?\loglog,基排就行)
其實樹哈希就是沒有最小表示法的字符串相關操作帶來的巨大復雜度而已。
哈希唯一的缺點就是會沖突,不能保證一定穩定,但也不至于很容易就被卡掉。
code
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define int long long #define mod 1019260817 #define base 19491001 #define maxn 55 struct node { int to, nxt; }E[maxn << 1]; pair < int, int > f[maxn], g[maxn]; int Pow[maxn], Hash[maxn], head[maxn], siz[maxn], MaxSiz[maxn], dep[maxn]; int n, m, cnt, rt1, rt2, Max;void addedge( int u, int v ) {E[cnt] = { v, head[u] };head[u] = cnt ++; }void dfs1( int u, int fa ) {siz[u] = 1;for( int i = head[u];~ i;i = E[i].nxt ) {int v = E[i].to;if( v == fa ) continue;dfs1( v, u );siz[u] += siz[v];MaxSiz[u] = max( MaxSiz[u], siz[v] );}MaxSiz[u] = max( MaxSiz[u], n - siz[u] );if( MaxSiz[u] < Max ) Max = MaxSiz[u], rt1 = u, rt2 = 0;else if( MaxSiz[u] == Max ) rt2 = u; }void dfs2( int u, int fa ) {Hash[u] = Pow[siz[u] = 1] * dep[u] % mod;for( int i = head[u];~ i;i = E[i].nxt ) if( E[i].to ^ fa ) dep[E[i].to] = dep[u] + 1, dfs2( E[i].to, u );int tot = 0;for( int i = head[u];~ i;i = E[i].nxt )if( E[i].to ^ fa ) g[++ tot] = { Hash[E[i].to], siz[E[i].to] };sort( g + 1, g + tot + 1 );for( int i = 1;i <= tot;i ++ )Hash[u] = ( Hash[u] + g[i].first * Pow[siz[u]] ) % mod, siz[u] += g[i].second; }signed main() {Pow[0] = 1;for( int i = 1;i < maxn;i ++ ) Pow[i] = Pow[i - 1] * base % mod;scanf( "%lld", &m );for( int k = 1;k <= m;k ++ ) {memset( MaxSiz, 0, sizeof( MaxSiz ) );memset( head, -1, sizeof( head ) );cnt = 0; Max = mod;scanf( "%lld", &n );for( int i = 1, x;i <= n;i ++ ) {scanf( "%lld", &x );if( x ) addedge( i, x ), addedge( x, i );}dfs1( 1, 0 );dep[rt1] = 1;dfs2( rt1, 0 );f[k].first = Hash[rt1];if( ! rt2 ) goto pass;dep[rt2] = 1;dfs2( rt2, 0 );f[k].second = Hash[rt2];if( f[k].first > f[k].second ) swap( f[k].first, f[k].second );pass : ; }for( int i = 1;i <= m;i ++ )for( int j = 1;j <= i;j ++ )if( f[i] == f[j] ) { printf( "%lld\n", j ); break; }return 0; }總結
以上是生活随笔為你收集整理的树的同构模板题(法1.最小表示法+法2.树哈希)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【无码专区4】幸运数字4(折半搜索+计数
- 下一篇: 7000 MB/s读速 + 2G 独立缓