有一顆大小為\(n\)的樹\(a\),現加上乙個節點並打亂編號,形成樹\(b\),詢問加上的節點最後編號是多少?
判斷樹的同構顯然需要樹雜湊。
可以先將樹\(a\)中以每個節點為根的雜湊值算出來存進乙隻\(unordered\_set\)中,
然後在樹\(b\)中隨便找乙個不是葉節點的節點為根,列舉去掉乙個葉節點,看根的\(hash\)值是否能在\(unordered\_set\)中找到。
什麼?只會\(o(n^2)\)求樹的雜湊值?
我們需要思考一種\(hash\)函式,在根變動時,只影響新根和原根兩節點的值,這樣就可以每列舉到乙個點,就算出其為根時的雜湊值。因為要先\(dp\)一遍才能換根,所以複雜度為\(o(2n)\)。
並且函式需要很容易去掉某個點的影響。(異或)
\[hash_=(hash_+base)\bigoplus(hash_+base)
\bigoplus ...+size_*p+1\]
一般樹雜湊還要考慮\(deep\),當然如果你要換根\(dp\),考慮\(deep\)的影響就沒什麼用啦。
#include#include#include#include#include#include#include#define ll unsigned long long
#define re register
#define il inline
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int n=5e5+10,p=1e9+7;
std::tr1::unordered_setq;
int h[n],cnt,in[n],sz[n],n;
ll hash[n],ans=1e18;
struct edgee[n<<1];
il void add(re int u,re int v);h[u]=cnt;}
il void dfs(re int u,re int fa)
hash[u]+=sz[u]*p+1;
}il void dfs1(re int u,re int fa)
}il void dfs2(re int u,re int fa)
else
}}int main()
dfs(1,0);//計算樹a雜湊值
dfs1(1,0);//換根算樹a雜湊值
memset(h,-1,sizeof(h));memset(hash,0,sizeof(hash));
fp(i,1,n)
re int i;
for(i=1;i<=n;i++) if(in[i]>1) break;//隨便找乙個不是葉節點的節點為根
dfs(i,0);//算樹b雜湊值
dfs2(i,0);//對葉子節點,試去掉它會怎麼樣;對非葉子節點,進行換根dp
printf("%lld\n",ans);
return 0;
}
JSOI2016 獨特的樹葉
點此看題 其實就是判斷樹同構,然後自然聯想到了樹has hhash hash 簡單介紹一下樹雜湊的方法,我們先求出子樹的has hhash hash 值g i g i g i 我用的是質數 自然溢位的方式,設p i p i p i 為第i ii個質數,那麼轉移 g u 1 g v p siz v g...
JSOI2016 獨特的樹葉
仙題 1.我們發現如果能夠求出來 a 樹中任何乙個點當根的時候的 hash 值,那麼就可以求出答案了。然後你隨便寫一寫 hash 策略改成 xor 發現 xor 的逆運算就是 xor 好啊!換根 dp 直接求出我們需要的,然後放到 set 去就行了。include include include i...
JSOI2016 獨特的樹葉(樹雜湊)
這個題只要求出以每個點為根的有根 無標號 樹的hash值就好了。我以前的樹雜湊是把樹轉為括號序,這個太麻煩了。一種方法是每個點的權值定義為siz,找到乙個dfs序,使得經過的點的權值字典序組最小。這個對於這道題也不方便,因為換根是可能需要字首和字尾和搞。在網上看到一種的hash是這個 f x 1 s...