BZOJ 4754 [JSOI2016]獨特的樹葉 | 樹哈希判同構

題目連接php

這道題是一道判斷無根樹同構的模板題,判斷同構主要的思路就是哈希。html

一遇到哈希題,一百我的能有一百零一種哈希方式,這篇題解隨便選用了一種——相似楊弋《Hash在信息學競賽中的一類應用》中的這種,可能不是最簡潔好寫的,可是能用。ios

個人哈希規則:子樹\(u\)的哈希值由它的每個子樹\(v_i\)的哈希值得來,首先將全部\(f(v)\)排個序(防止順序不一樣形成影響),而後\(f(u) = size(u) * \sum_i f(v_i)W^{i - 1} \bmod P\)\(W\)是事先選取的一個位權,\(P\)是模數,\(size(u)\)是子樹\(u\)的大小。數組

這樣DFS一遍可求出以\(1\)號節點爲根時,全部子樹的哈希值\(f(u)\)spa

可是這是無根樹,咱們想求出以任意節點爲根時整棵樹的哈希值。3d

\(fa_u\)爲以\(1\)爲根時\(u\)的父親,則上面的\(f(u)\)也是以\(fa_u\)爲根時子樹\(u\)的哈希值。code

再求一個\(g(u)\)表示以\(u\)爲根時子樹\(fa_u\)的哈希值。這個\(g(u)\)怎麼求呢?再DFS一遍,對於每一個節點,\(g(u)\)\(g(fa_u)\)以及\(u\)的每一個兄弟\(v_i\)\(f(v_i)\)得來。可是直接暴力枚舉的話在菊花圖上是\(O(n^2)\)的,那怎麼辦呢?htm

對於每一個節點\(u\)維護一個數組,存儲它全部兒子的哈希值\(f(v)\),若是有父親,則\(g(u)\)也在裏面,把這個數組排好序,求出每一個前綴的哈希值和每一個後綴的哈希值。這時,以\(u\)爲根時整棵樹的哈希值就是整個數組的哈希值(再乘上子樹大小\(n\))。get

此時求每一個兒子\(v\)\(g(v)\),就是從那個數組中間去掉\(f(v)\)後的哈希值,二分查找後把前綴哈希值和後綴哈希值拼起來就能夠獲得。記得乘上\(v\)爲根時\(u\)\(size\)\(n - size(v)\)string

這樣就求出以每一個節點爲根的哈希值了。

把A的全部哈希值存到一個set裏,而後枚舉B的每一個度爲1的點\(u\),求出以\(u\)爲根它的惟一子樹\(v\)的哈希值,若是set裏有這個值,\(u\)就是所求的點之一。

代碼比較醜,見諒 ><

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <set>
#define space putchar(' ')
#define enter putchar('\n')
typedef long long ll;
using namespace std;
template <class T>
void read(T &x){
    char c;
    bool op = 0;
    while(c = getchar(), c < '0' || c > '9')
        if(c == '-') op = 1;
    x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
        x = x * 10 + c - '0';
    if(op) x = -x;
}
template <class T>
void write(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x >= 10) write(x / 10);
    putchar('0' + x % 10);
}

const int N = 100005, W = 1000000021, P = 999999137;
int n, m, fa[N], f[N], g[N], pw[N], Sze[N], deg[N], ans = P;
int ecnt, adj[N], nxt[2*N], go[2*N];
vector <int> son[N], sl[N], sr[N];
set <int> vis;
bool isB;

void add(int u, int v){
    if(isB) deg[u]++;
    go[++ecnt] = v;
    nxt[ecnt] = adj[u];
    adj[u] = ecnt;
}
int dfs1(int u, int pre){
    Sze[u] = 1;
    fa[u] = pre;
    son[u].clear();
    for(int e = adj[u], v; e; e = nxt[e])
    if((v = go[e]) != pre){
        son[u].push_back(dfs1(v, u));
        Sze[u] += Sze[v];
    }
    if(son[u].empty()) return f[u] = 1;
    sort(son[u].begin(), son[u].end());
    ll ret = 0;
    for(int i = 0; i < (int)son[u].size(); i++)
    ret = (ret * W + son[u][i]) % P;
    return f[u] = Sze[u] * ret % P;
}

void dfs2(int u){
    if(fa[u]){
    son[u].push_back(g[u]);
    sort(son[u].begin(), son[u].end());
    }
    int sze = son[u].size();
    sl[u].resize(sze);
    sl[u][0] = son[u][0];
    for(int i = 1; i < sze; i++)
    sl[u][i] = ((ll)sl[u][i - 1] * W + son[u][i]) % P;
    sr[u].resize(sze);
    sr[u][sze - 1] = son[u][sze - 1];
    for(int i = sze - 2; i >= 0; i--)
    sr[u][i] = (sr[u][i + 1] + (ll)son[u][i] * pw[sze - i - 1]) % P;
    for(int e = adj[u], v; e; e = nxt[e])
    if((v = go[e]) != fa[u]){
        if(sze == 1){
        g[v] = 1;
        dfs2(v);
        break;
        }
        int p = lower_bound(son[u].begin(), son[u].end(), f[v]) - son[u].begin();
        g[v] = 0;
        if(p + 1 < sze) g[v] = sr[u][p + 1];
        if(p - 1 >= 0) g[v] = (g[v] + (ll)sl[u][p - 1] * pw[sze - 1 - p]) % P;
        g[v] = (ll)g[v] * (n - Sze[v]) % P;
        if(isB && deg[v] == 1 && vis.find(g[v]) != vis.end()) ans = min(ans, v);
        dfs2(v);
    }
    if(!isB) vis.insert((ll)sl[u][sze - 1] * n % P);
}

int main(){

    pw[0] = 1;
    for(int i = 1; i < N; i++)
    pw[i] = (ll)pw[i - 1] * W % P;
    read(n);
    for(int i = 1, u, v; i < n; i++)
    read(u), read(v), add(u, v), add(v, u);
    dfs1(1, 0);
    dfs2(1);
    ecnt = 0, isB = 1, n++;
    memset(adj, 0, sizeof(adj));
    for(int i = 1, u, v; i < n; i++)
    read(u), read(v), add(u, v), add(v, u);
    dfs1(1, 0);
    if(deg[1] == 1 && vis.find(f[go[adj[1]]]) != vis.end())
    ans = 1;
    dfs2(1);
    write(ans), enter;

    return 0;
}
相關文章
相關標籤/搜索