Problem:
F: 木を隠すのなら森の中
物語
「木はいいです」
喫茶店「タートルハウス」を経営するチコちゃんは、木に並々ならぬ愛情を持っている。なんでも、木の不動のあり方に心が洗われるらしい。。。そんなチコちゃんの木への愛情を確かめるため、同じ喫茶店でバイトをしているココロちゃんはチコちゃんにいたずらを仕掛けることにした。
ココロちゃんは、チコちゃんのために森を用意して、喫茶店近くに生えていた木を何本か森の中に隠し、チコちゃんの木への愛情を試すことにした。具体的には、チコちゃんに、隠した木を森の中から見つけてもらうことにした。木をこよなく愛するチコちゃんなら、一度見たことがある木は、心の洗われ方の違いでわかるのだが、木を移動してしまったため、木の不動のあり方が変わってしまい、どの木が喫茶店近くにあった木かわからなくなってしまった。しかも、ドジなココロちゃんも答えの木がどれか忘れてしまった！このままでは答えの木を元の場所に戻せないので、ココロちゃんがチコちゃんに怒られてしまう。そこで、優しいプログラマーの皆さんでココロちゃんに答えの木が何本あるのか教えてあげるプログラムを書いてあげよう。
問題
入力として、2つのグラフ
G_1
と
G_2
が与えられる。
G_1
は森、つまり閉路のないグラフ（連結とは限らない）で、
G_2
は木、つまり閉路のない連結なグラフである。このとき、
G_1
が
G_2
と同型な連結成分をいくつ持つのかを求めよ
注1）グラフ
G_1
のある連結成分
H_1
に含まれる任意の2頂点
u, v
に対して、
u, v
が隣接する場合にのみ、
G_2
中の2頂点
f(u), f(v)
が隣接するような全単射
f
が存在するとき、
H_1
は、
G_2
と同型な
G_1
の連結成分であるという。
入力
入力は以下の形式で与えられる。
N_1
M_1
u_1
v_1
...
u_{M_1}
v_{M_1}
N_2
w_1
x_1
...
w_{N_2 - 1}
x_{N_2 - 1}
1行目では、森
G_1
の頂点数
N_1（1 \leq N_1 \leq 300,000）
と辺数
M_1（0 \leq M_1 \leq N_1 - 1）
が与えられる。続く
M_1
行では、
i
行目で
G_1
の
i
番目の辺
e_i
が与えられる
(1 \leq i \leq M_1)
。
e_i
は2頂点
u_i
と
v_i
をつなぐ辺である。
（1 \leq u_i, v_i \leq N_1, u_i \neq v_i）
次の行には木
G_2
の頂点数
N_2（1 \leq N_2 \leq 30,000）
が与えられる。続く
N_2 - 1
行では、
j
行目で
G_2
の
j
番目の辺
e_j
が与えられる
(1 \leq j \leq N_2 - 1)
。
e_j
は2頂点
w_j
と
x_j
をつなぐ辺である。
（1 \leq w_j, x_j \leq N_2, w_j \neq x_j）
出力
G_1
の持つ
G_2
と同型な連結成分の数を出力せよ。
入力例1
6 4
1 2
2 3
2 4
5 6
4
2 3
3 1
3 4
出力例1
1
入力例2
14 9
5 8
5 11
10 14
6 4
13 9
3 14
1 2
6 12
7 9
3
3 1
2 1
出力例2
4

Submitted solutions:

code_snippet: // AOJ #2821
// Trees in a Forest 2025.2.5

#include <bits/stdc++.h>
using namespace std;

#define gc() getchar_unlocked()
#define pc(c) putchar_unlocked(c)

int Cin() {  // 整数の入力
	int n = 0, c = gc();
	if (c == '-') {	c = gc();
		do n = 10*n + (c & 0xf), c = gc(); while (c >= '0');
		return -n;
	}
	do n = 10*n + (c & 0xf), c = gc(); while (c >= '0');
	return n;
}

void Cout(int n) { // 整数の表示（出力）
	char b[30];
	if (!n) pc('0');
	else {
		if (n < 0) pc('-'), n = -n;
		int i = 0; while (n) b[i++] = n % 10 + '0', n /= 10;
		while (i--) pc(b[i]);
	}
	pc('\n');
}

// --- AHUアルゴリズムによる木の正準形の生成 ---
// 再帰的に，頂点 u を根とする部分木の表現を返す．
string encodeTree(int u, int parent, const vector<vector<int>> &graph) {
    vector<string> children;
    for (int v : graph[u]) {
        if(v == parent) continue;
        children.push_back(encodeTree(v, u, graph));
    }
    sort(children.begin(), children.end());
    string s = "(";
    for (auto &child : children)
        s += child;
    s += ")";
    return s;
}
 
// --- 木の中心を求める ---
// comp に含まれる頂点（G1 または G2 の連結成分）について，
// 葉刈り法で中心（1個または2個）を求める．
vector<int> findCenters(const vector<int>& comp, const vector<vector<int>> &graph) {
    int n = comp.size();
    unordered_set<int> compSet(comp.begin(), comp.end());
    unordered_map<int,int> deg;
    queue<int> leaves;
    for (int u : comp) {
        int d = 0;
        for (int nb : graph[u])
            if(compSet.count(nb))
                d++;
        deg[u] = d;
        if(d <= 1)
            leaves.push(u);
    }
    int remaining = n;
    while(remaining > 2) {
        int sz = leaves.size();
        for (int i = 0; i < sz; i++){
            int leaf = leaves.front();
            leaves.pop();
            remaining--;
            compSet.erase(leaf);
            for (int nb : graph[leaf]) {
                if(compSet.count(nb)){
                    deg[nb]--;
                    if(deg[nb] == 1)
                        leaves.push(nb);
                }
            }
        }
    }
    vector<int> centers;
    while(!leaves.empty()){
        centers.push_back(leaves.front());
        leaves.pop();
    }
    return centers;
}
 
// --- 木の正準形（canonical form）を求める ---
// graph は木（あるいは G1 の連結成分）で，
// vertices はその頂点集合（1-indexedの番号）．
string treeCanonicalForm(const vector<vector<int>> &graph, const vector<int>& vertices) {
    vector<int> centers = findCenters(vertices, graph);
    string best = "";
    for (int center : centers) {
        string code = encodeTree(center, -1, graph);
        if(best == "" || code < best)
            best = code;
    }
    return best;
}
 
int main() {
    // --- 入力 (G1: 森) ---
    int N1 = Cin(), M1 = Cin();
    vector<vector<int>> graph1(N1+1);
    for (int i = 0; i < M1; i++){
        int u = Cin(), v = Cin();
        graph1[u].push_back(v);
        graph1[v].push_back(u);
    }
 
    // --- 入力 (G2: 木) ---
    int N2 = Cin();
    vector<vector<int>> graph2(N2+1);
    for (int i = 0; i < N2-1; i++){
        int u = Cin(), v = Cin();
        graph2[u].push_back(v);
        graph2[v].push_back(u);
    }
 
    // --- G2 の正準形を計算 ---
    vector<int> verticesG2;
    for (int i = 1; i <= N2; i++)
        verticesG2.push_back(i);
    string canonG2 = treeCanonicalForm(graph2, verticesG2);
 
    // --- G1 の各連結成分について調べる ---
    // G1 は森なので，各連結成分は木（閉路なし）である．
    // ただし，G2 と同型になるためには頂点数も一致していなければならない．
    int answer = 0;
    vector<bool> visited(N1+1, false);
    for (int i = 1; i <= N1; i++){
        if (!visited[i]){
            // BFS で連結成分を求める．
            vector<int> comp;
            queue<int> q;
            q.push(i);
            visited[i] = true;
            while(!q.empty()){
                int cur = q.front();
                q.pop();
                comp.push_back(cur);
                for (int nb : graph1[cur]){
                    if(!visited[nb]){
                        visited[nb] = true;
                        q.push(nb);
                    }
                }
            }
            // 頂点数が一致している場合のみ判定する．
            if ((int)comp.size() == N2) {
                string canonComp = treeCanonicalForm(graph1, comp);
                if(canonComp == canonG2)
                    answer++;
            }
        }
    }
    Cout(answer);
    return 0;
}
accuracy: 1
time_ms: 210
memory_kb: 27276
final_rank: 1

code_snippet: #include<iostream>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;
const int N = 3e5 + 10;

int n1, m1, n2;
vector<vector<int> > g1, g2;
map<vector<int>, int> hsh;
int sz[N];
bool usd[N];

int dfs(int v, int p, vector<vector<int> > &g) {
    vector<int> cur;
    usd[v] = true;
    for(int j = 0; j < (int)g[v].size(); ++j) {
        int to = g[v][j];
        if(to == p) continue;
        cur.push_back(dfs(to, v, g));
    }
    sort(cur.begin(), cur.end());
    if(hsh[cur] == 0) hsh[cur] = (int)hsh.size() + 1;
    return hsh[cur];
}

void calc_sz(int v, int p, vector<vector<int> > &g) {
    sz[v] = 1;
    for(int j = 0; j < (int)g[v].size(); ++j) {
        int to = g[v][j];
        if(to == p) continue;
        calc_sz(to, v, g);
        sz[v] += sz[to];
    }
}

vector<int> find_c(int v, int p, int csz, vector<vector<int> > &g) {
    for(int j = 0; j < (int)g[v].size(); ++j) {
        int to = g[v][j];
        if(to == p) continue;
        if(2 * sz[to] == csz) {
            vector<int> ans(1, v);
            ans.push_back(v);
            return ans;
        }
        else if(2 * sz[to] > csz) return find_c(to, v, csz, g);
    }
    vector<int> ans(1, v);
    return ans;
}

vector<int> centroids(int v, vector<vector<int> > &g) {
    calc_sz(v, 0, g);
    return find_c(v, 0, sz[v], g);
}

int main() {
    cin >> n1 >> m1;
    g1.resize(n1 + 1);
    for(int i = 0; i < m1; ++i) {
        int u, v; cin >> u >> v;
        g1[u].push_back(v);
        g1[v].push_back(u);
    }
    cin >> n2;
    g2.resize(n2 + 1);
    for(int i = 0; i < n2 - 1; ++i) {
        int u, v; cin >> u >> v;
        g2[u].push_back(v);
        g2[v].push_back(u);
    }
    vector<int> good;
    vector<int> c = centroids(1, g2);
    for(int i = 0; i < (int)c.size(); ++i) good.push_back(dfs(c[i], 0, g2));

    int ans = 0;
    fill(usd, usd + n1 + 1, false);
    for(int i = 1; i <= n1; ++i) {
        if(!usd[i]) {
            c = centroids(i, g1);
            bool f = false;
            for(int j = 0; j < (int)c.size(); ++j) {
                int h = dfs(c[j], 0, g1);
                for(int x = 0; x < (int)good.size(); ++x) if(h == good[x]) f = true;
            }
            ans += f;
        }
    }
    cout << ans << '\n';
    return 0;
}
accuracy: 1
time_ms: 590
memory_kb: 64624
final_rank: 12

code_snippet: #include<iostream>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;
const int N = 300010;

int n1, m1, n2;
vector<int> g[2][N];
map<vector<int>, int> hsh;
int sz[N];
bool usd[N];

int dfs(int v, int p, int i) {
    vector<int> cur;
    usd[v] = true;
    for(int j = 0; j < (int)g[i][v].size(); ++j) {
        int to = g[i][v][j];
        if(to == p) continue;
        cur.push_back(dfs(to, v, i));
    }
    sort(cur.begin(), cur.end());
    if(hsh[cur] == 0) hsh[cur] = (int)hsh.size() + 1;
    return hsh[cur];
}

void calc_sz(int v, int p, int i) {
    sz[v] = 1;
    for(int j = 0; j < (int)g[i][v].size(); ++j) {
        int to = g[i][v][j];
        if(to == p) continue;
        calc_sz(to, v, i);
        sz[v] += sz[to];
    }
}

vector<int> find_c(int v, int p, int csz, int i) {
    for(int j = 0; j < (int)g[i][v].size(); ++j) {
        int to = g[i][v][j];
        if(to == p) continue;
        if(2 * sz[to] == csz) {
            vector<int> ans;
            ans.push_back(to);
            ans.push_back(v);
            return ans;
        }
        else if(2 * sz[to] > csz) return find_c(to, v, csz, i);
    }
    vector<int> ans;
    ans.push_back(v);
    return ans;
}

vector<int> centroids(int v, int i) {
    calc_sz(v, 0, i);
    return find_c(v, 0, sz[v], i);
}

int main() {
#ifdef LOCAL
    freopen("inp.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    cin >> n1 >> m1;
    for(int i = 0; i < m1; ++i) {
        int u, v; cin >> u >> v;
        g[0][u].push_back(v);
        g[0][v].push_back(u);
    }
    cin >> n2;
    for(int i = 0; i < n2 - 1; ++i) {
        int u, v; cin >> u >> v;
        g[1][u].push_back(v);
        g[1][v].push_back(u);
    }
    vector<int> good;
    vector<int> c = centroids(1, 1);
    for(int i = 0; i < (int)c.size(); ++i) good.push_back(dfs(c[i], 0, 1));

    int ans = 0;
    fill(usd, usd + n1 + 1, false);
    for(int i = 1; i <= n1; ++i) {
        if(!usd[i]) {
            c = centroids(i, 0);
            bool f = false;
            for(int j = 0; j < (int)c.size(); ++j) {
                int h = dfs(c[j], 0, 0);
                for(int x = 0; x < (int)good.size(); ++x) if(h == good[x]) f = true;
            }
            ans += f;
        }
    }
    cout << ans << '\n';
    return 0;
}
accuracy: 1
time_ms: 600
memory_kb: 71132
final_rank: 14

code_snippet: #include<iostream>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;
const int N = 3e5 + 10;

int n1, m1, n2, sz[N];
bool usd[N];
vector<vector<int> > g1, g2;
map<vector<int>, int> hsh;

int dfs(int v, int p, vector<vector<int> > &g) {
    vector<int> cur;
    usd[v] = true;
    for(int to : g[v]) {
        if(to == p) continue;
        cur.push_back(dfs(to, v, g));
    }
    sort(cur.begin(), cur.end());
    if(hsh.find(cur) == hsh.end()) hsh[cur] = (int)hsh.size();
    return hsh[cur];
}

void calc_sz(int v, int p, vector<vector<int> > &g) {
    sz[v] = 1;
    for(int to : g[v]) {
        if(to == p) continue;
        calc_sz(to, v, g);
        sz[v] += sz[to];
    }
}

vector<int> find_c(int v, int p, int csz, vector<vector<int> > &g) {
    for(int to : g[v]) {
        if(to == p) continue;
        if(2 * sz[to] == csz) {
            vector<int> ans(1, v);
            ans.push_back(to);
            return ans;
        }
        else if(2 * sz[to] > csz) return find_c(to, v, csz, g);
    }
    vector<int> ans(1, v);
    return ans;
}

vector<int> centroids(int v, vector<vector<int> > &g) {
    calc_sz(v, 0, g);
    return find_c(v, 0, sz[v], g);
}

int main() {
    cin >> n1 >> m1;
    g1.resize(n1 + 1);
    for(int i = 0; i < m1; ++i) {
        int u, v; cin >> u >> v;
        g1[u].push_back(v);
        g1[v].push_back(u);
    }
    cin >> n2;
    g2.resize(n2 + 1);
    for(int i = 0; i < n2 - 1; ++i) {
        int u, v; cin >> u >> v;
        g2[u].push_back(v);
        g2[v].push_back(u);
    }
    vector<int> good;
    vector<int> c = centroids(1, g2);
    for(int root : c) good.push_back(dfs(root, 0, g2));

    int ans = 0;
    fill(usd, usd + n1 + 1, false);
    for(int i = 1; i <= n1; ++i) {
        if(!usd[i]) {
            c = centroids(i, g1);
            bool f = false;
            for(int root : c) {
                int h = dfs(root, 0, g1);
                for(int h1 : good) if(h == h1) f = true;
            }
            ans += f;
        }
    }
    cout << ans << '\n';
}
accuracy: 1
time_ms: 380
memory_kb: 55396
final_rank: 8

code_snippet: #include<iostream>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;
const int N = 3e5 + 10;

int n1, m1, n2, sz[N];
bool usd[N];
vector<vector<int> > g1, g2;
map<vector<int>, int> hsh;

int dfs(int v, int p, vector<vector<int> > &g) {
    vector<int> cur;
    usd[v] = true;
    for(int to : g[v]) {
        if(to == p) continue;
        cur.push_back(dfs(to, v, g));
    }
    sort(cur.begin(), cur.end());
    if(hsh.find(cur) == hsh.end()) hsh[cur] = (int)hsh.size();
    return hsh[cur];
}

void calc_sz(int v, int p, vector<vector<int> > &g) {
    sz[v] = 1;
    for(int to : g[v]) {
        if(to == p) continue;
        calc_sz(to, v, g);
        sz[v] += sz[to];
    }
}

vector<int> find_c(int v, int p, int csz, vector<vector<int> > &g) {
    for(int to : g[v]) {
        if(to == p) continue;
        if(2 * sz[to] == csz) return {v, to};
        else if(2 * sz[to] > csz) return find_c(to, v, csz, g);
    }
    return {v};
}

vector<int> centroids(int v, vector<vector<int> > &g) {
    calc_sz(v, 0, g);
    return find_c(v, 0, sz[v], g);
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
#ifdef LOCAL
    freopen("inp.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    cin >> n1 >> m1;
    g1.resize(n1 + 1);
    for(int i = 0; i < m1; ++i) {
        int u, v; cin >> u >> v;
        g1[u].push_back(v);
        g1[v].push_back(u);
    }
    cin >> n2;
    g2.resize(n2 + 1);
    for(int i = 0; i < n2 - 1; ++i) {
        int u, v; cin >> u >> v;
        g2[u].push_back(v);
        g2[v].push_back(u);
    }
    vector<int> good;
    vector<int> c = centroids(1, g2);
    for(int root : c) good.push_back(dfs(root, 0, g2));

    int ans = 0;
    fill(usd, usd + n1 + 1, false);
    for(int i = 1; i <= n1; ++i) {
        if(!usd[i]) {
            c = centroids(i, g1);
            bool f = false;
            for(int root : c) {
                int h = dfs(root, 0, g1);
                for(int h1 : good) if(h == h1) f = true;
            }
            ans += f;
        }
    }
    cout << ans << '\n';
}
accuracy: 1
time_ms: 330
memory_kb: 55240
final_rank: 6

code_snippet: #include<iostream>
#include<vector>
#include<map>
#include<set>

using namespace std;
const int N = 3e5 + 10;

int n1, m1, n2, sz[N];
bool usd[N];
vector<vector<int>> g1, g2;
map<multiset<int>, int> hsh;

int dfs(int v, int p, vector<vector<int>> &g) {
    multiset<int> cur;
    usd[v] = true;
    for(int to : g[v]) {
        if(to == p) continue;
        cur.insert(dfs(to, v, g));
    }
    if(hsh.find(cur) == hsh.end()) hsh[cur] = (int)hsh.size();
    return hsh[cur];
}

void calc_sz(int v, int p, vector<vector<int>> &g) {
    sz[v] = 1;
    for(int to : g[v]) {
        if(to == p) continue;
        calc_sz(to, v, g);
        sz[v] += sz[to];
    }
}

vector<int> find_c(int v, int p, int csz, vector<vector<int>> &g) {
    for(int to : g[v]) {
        if(to == p) continue;
        if(2 * sz[to] == csz) return {v, to};
        else if(2 * sz[to] > csz) return find_c(to, v, csz, g);
    }
    return {v};
}

vector<int> centroids(int v, vector<vector<int>> &g) {
    calc_sz(v, 0, g);
    return find_c(v, 0, sz[v], g);
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
#ifdef LOCAL
    freopen("inp.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    cin >> n1 >> m1;
    g1.resize(n1 + 1);
    for(int i = 0; i < m1; ++i) {
        int u, v; cin >> u >> v;
        g1[u].push_back(v);
        g1[v].push_back(u);
    }
    cin >> n2;
    g2.resize(n2 + 1);
    for(int i = 0; i < n2 - 1; ++i) {
        int u, v; cin >> u >> v;
        g2[u].push_back(v);
        g2[v].push_back(u);
    }
    vector<int> good;
    vector<int> c = centroids(1, g2);
    for(int root : c) good.push_back(dfs(root, 0, g2));

    int ans = 0;
    fill(usd, usd + n1 + 1, false);
    for(int i = 1; i <= n1; ++i) {
        if(!usd[i]) {
            c = centroids(i, g1);
            bool f = false;
            for(int root : c) {
                int h = dfs(root, 0, g1);
                for(int h1 : good) if(h == h1) f = true;
            }
            ans += f;
        }
    }
    cout << ans << '\n';
}
accuracy: 1
time_ms: 360
memory_kb: 66892
final_rank: 9

code_snippet: /**
 * date   : 2024-05-04 15:40:25
 * author : Nyaan
 */

#define NDEBUG

using namespace std;

// intrinstic
#include <immintrin.h>

#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <cctype>
#include <cfenv>
#include <cfloat>
#include <chrono>
#include <cinttypes>
#include <climits>
#include <cmath>
#include <complex>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <fstream>
#include <functional>
#include <initializer_list>
#include <iomanip>
#include <ios>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <random>
#include <set>
#include <sstream>
#include <stack>
#include <streambuf>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

// utility

namespace Nyaan {
using ll = long long;
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128_t;
using u128 = __uint128_t;

template <typename T>
using V = vector<T>;
template <typename T>
using VV = vector<vector<T>>;
using vi = vector<int>;
using vl = vector<long long>;
using vd = V<double>;
using vs = V<string>;
using vvi = vector<vector<int>>;
using vvl = vector<vector<long long>>;
template <typename T>
using minpq = priority_queue<T, vector<T>, greater<T>>;

template <typename T, typename U>
struct P : pair<T, U> {
  template <typename... Args>
  P(Args... args) : pair<T, U>(args...) {}

  using pair<T, U>::first;
  using pair<T, U>::second;

  P &operator+=(const P &r) {
    first += r.first;
    second += r.second;
    return *this;
  }
  P &operator-=(const P &r) {
    first -= r.first;
    second -= r.second;
    return *this;
  }
  P &operator*=(const P &r) {
    first *= r.first;
    second *= r.second;
    return *this;
  }
  template <typename S>
  P &operator*=(const S &r) {
    first *= r, second *= r;
    return *this;
  }
  P operator+(const P &r) const { return P(*this) += r; }
  P operator-(const P &r) const { return P(*this) -= r; }
  P operator*(const P &r) const { return P(*this) *= r; }
  template <typename S>
  P operator*(const S &r) const {
    return P(*this) *= r;
  }
  P operator-() const { return P{-first, -second}; }
};

using pl = P<ll, ll>;
using pi = P<int, int>;
using vp = V<pl>;

constexpr int inf = 1001001001;
constexpr long long infLL = 4004004004004004004LL;

template <typename T>
int sz(const T &t) {
  return t.size();
}

template <typename T, typename U>
inline bool amin(T &x, U y) {
  return (y < x) ? (x = y, true) : false;
}
template <typename T, typename U>
inline bool amax(T &x, U y) {
  return (x < y) ? (x = y, true) : false;
}

template <typename T>
inline T Max(const vector<T> &v) {
  return *max_element(begin(v), end(v));
}
template <typename T>
inline T Min(const vector<T> &v) {
  return *min_element(begin(v), end(v));
}
template <typename T>
inline long long Sum(const vector<T> &v) {
  return accumulate(begin(v), end(v), 0LL);
}

template <typename T>
int lb(const vector<T> &v, const T &a) {
  return lower_bound(begin(v), end(v), a) - begin(v);
}
template <typename T>
int ub(const vector<T> &v, const T &a) {
  return upper_bound(begin(v), end(v), a) - begin(v);
}

constexpr long long TEN(int n) {
  long long ret = 1, x = 10;
  for (; n; x *= x, n >>= 1) ret *= (n & 1 ? x : 1);
  return ret;
}

template <typename T, typename U>
pair<T, U> mkp(const T &t, const U &u) {
  return make_pair(t, u);
}

template <typename T>
vector<T> mkrui(const vector<T> &v, bool rev = false) {
  vector<T> ret(v.size() + 1);
  if (rev) {
    for (int i = int(v.size()) - 1; i >= 0; i--) ret[i] = v[i] + ret[i + 1];
  } else {
    for (int i = 0; i < int(v.size()); i++) ret[i + 1] = ret[i] + v[i];
  }
  return ret;
};

template <typename T>
vector<T> mkuni(const vector<T> &v) {
  vector<T> ret(v);
  sort(ret.begin(), ret.end());
  ret.erase(unique(ret.begin(), ret.end()), ret.end());
  return ret;
}

template <typename F>
vector<int> mkord(int N, F f) {
  vector<int> ord(N);
  iota(begin(ord), end(ord), 0);
  sort(begin(ord), end(ord), f);
  return ord;
}

template <typename T>
vector<int> mkinv(vector<T> &v) {
  int max_val = *max_element(begin(v), end(v));
  vector<int> inv(max_val + 1, -1);
  for (int i = 0; i < (int)v.size(); i++) inv[v[i]] = i;
  return inv;
}

vector<int> mkiota(int n) {
  vector<int> ret(n);
  iota(begin(ret), end(ret), 0);
  return ret;
}

template <typename T>
T mkrev(const T &v) {
  T w{v};
  reverse(begin(w), end(w));
  return w;
}

template <typename T>
bool nxp(T &v) {
  return next_permutation(begin(v), end(v));
}

// 返り値の型は入力の T に依存
// i 要素目 : [0, a[i])
template <typename T>
vector<vector<T>> product(const vector<T> &a) {
  vector<vector<T>> ret;
  vector<T> v;
  auto dfs = [&](auto rc, int i) -> void {
    if (i == (int)a.size()) {
      ret.push_back(v);
      return;
    }
    for (int j = 0; j < a[i]; j++) v.push_back(j), rc(rc, i + 1), v.pop_back();
  };
  dfs(dfs, 0);
  return ret;
}

// F : function(void(T&)), mod を取る操作
// T : 整数型のときはオーバーフローに注意する
template <typename T>
T Power(T a, long long n, const T &I, const function<void(T &)> &f) {
  T res = I;
  for (; n; f(a = a * a), n >>= 1) {
    if (n & 1) f(res = res * a);
  }
  return res;
}
// T : 整数型のときはオーバーフローに注意する
template <typename T>
T Power(T a, long long n, const T &I = T{1}) {
  return Power(a, n, I, function<void(T &)>{[](T &) -> void {}});
}

template <typename T>
T Rev(const T &v) {
  T res = v;
  reverse(begin(res), end(res));
  return res;
}

template <typename T>
vector<T> Transpose(const vector<T> &v) {
  using U = typename T::value_type;
  if(v.empty()) return {};
  int H = v.size(), W = v[0].size();
  vector res(W, T(H, U{}));
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      res[j][i] = v[i][j];
    }
  }
  return res;
}

template <typename T>
vector<T> Rotate(const vector<T> &v, int clockwise = true) {
  using U = typename T::value_type;
  int H = v.size(), W = v[0].size();
  vector res(W, T(H, U{}));
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      if (clockwise) {
        res[W - 1 - j][i] = v[i][j];
      } else {
        res[j][H - 1 - i] = v[i][j];
      }
    }
  }
  return res;
}

}  // namespace Nyaan


// bit operation

namespace Nyaan {
__attribute__((target("popcnt"))) inline int popcnt(const u64 &a) {
  return __builtin_popcountll(a);
}
inline int lsb(const u64 &a) { return a ? __builtin_ctzll(a) : 64; }
inline int ctz(const u64 &a) { return a ? __builtin_ctzll(a) : 64; }
inline int msb(const u64 &a) { return a ? 63 - __builtin_clzll(a) : -1; }
template <typename T>
inline int gbit(const T &a, int i) {
  return (a >> i) & 1;
}
template <typename T>
inline void sbit(T &a, int i, bool b) {
  if (gbit(a, i) != b) a ^= T(1) << i;
}
constexpr long long PW(int n) { return 1LL << n; }
constexpr long long MSK(int n) { return (1LL << n) - 1; }
}  // namespace Nyaan


// inout

namespace Nyaan {

template <typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
  os << p.first << " " << p.second;
  return os;
}
template <typename T, typename U>
istream &operator>>(istream &is, pair<T, U> &p) {
  is >> p.first >> p.second;
  return is;
}

template <typename T>
ostream &operator<<(ostream &os, const vector<T> &v) {
  int s = (int)v.size();
  for (int i = 0; i < s; i++) os << (i ? " " : "") << v[i];
  return os;
}
template <typename T>
istream &operator>>(istream &is, vector<T> &v) {
  for (auto &x : v) is >> x;
  return is;
}

istream &operator>>(istream &is, __int128_t &x) {
  string S;
  is >> S;
  x = 0;
  int flag = 0;
  for (auto &c : S) {
    if (c == '-') {
      flag = true;
      continue;
    }
    x *= 10;
    x += c - '0';
  }
  if (flag) x = -x;
  return is;
}

istream &operator>>(istream &is, __uint128_t &x) {
  string S;
  is >> S;
  x = 0;
  for (auto &c : S) {
    x *= 10;
    x += c - '0';
  }
  return is;
}

ostream &operator<<(ostream &os, __int128_t x) {
  if (x == 0) return os << 0;
  if (x < 0) os << '-', x = -x;
  string S;
  while (x) S.push_back('0' + x % 10), x /= 10;
  reverse(begin(S), end(S));
  return os << S;
}
ostream &operator<<(ostream &os, __uint128_t x) {
  if (x == 0) return os << 0;
  string S;
  while (x) S.push_back('0' + x % 10), x /= 10;
  reverse(begin(S), end(S));
  return os << S;
}

void in() {}
template <typename T, class... U>
void in(T &t, U &...u) {
  cin >> t;
  in(u...);
}

void out() { cout << "\n"; }
template <typename T, class... U, char sep = ' '>
void out(const T &t, const U &...u) {
  cout << t;
  if (sizeof...(u)) cout << sep;
  out(u...);
}

struct IoSetupNya {
  IoSetupNya() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(15);
    cerr << fixed << setprecision(7);
  }
} iosetupnya;

}  // namespace Nyaan


// debug


#ifdef NyaanDebug
#define trc(...) (void(0))
#else
#define trc(...) (void(0))
#endif

#ifdef NyaanLocal
#define trc2(...) (void(0))
#else
#define trc2(...) (void(0))
#endif


// macro

#define each(x, v) for (auto&& x : v)
#define each2(x, y, v) for (auto&& [x, y] : v)
#define all(v) (v).begin(), (v).end()
#define rep(i, N) for (long long i = 0; i < (long long)(N); i++)
#define repr(i, N) for (long long i = (long long)(N)-1; i >= 0; i--)
#define rep1(i, N) for (long long i = 1; i <= (long long)(N); i++)
#define repr1(i, N) for (long long i = (N); (long long)(i) > 0; i--)
#define reg(i, a, b) for (long long i = (a); i < (b); i++)
#define regr(i, a, b) for (long long i = (b)-1; i >= (a); i--)
#define fi first
#define se second
#define ini(...)   \
  int __VA_ARGS__; \
  in(__VA_ARGS__)
#define inl(...)         \
  long long __VA_ARGS__; \
  in(__VA_ARGS__)
#define ins(...)      \
  string __VA_ARGS__; \
  in(__VA_ARGS__)
#define in2(s, t)                           \
  for (int i = 0; i < (int)s.size(); i++) { \
    in(s[i], t[i]);                         \
  }
#define in3(s, t, u)                        \
  for (int i = 0; i < (int)s.size(); i++) { \
    in(s[i], t[i], u[i]);                   \
  }
#define in4(s, t, u, v)                     \
  for (int i = 0; i < (int)s.size(); i++) { \
    in(s[i], t[i], u[i], v[i]);             \
  }
#define die(...)             \
  do {                       \
    Nyaan::out(__VA_ARGS__); \
    return;                  \
  } while (0)


namespace Nyaan {
void solve();
}
int main() { Nyaan::solve(); }


//


struct UnionFind {
  vector<int> data;
  UnionFind(int N) : data(N, -1) {}

  int find(int k) { return data[k] < 0 ? k : data[k] = find(data[k]); }

  int unite(int x, int y) {
    if ((x = find(x)) == (y = find(y))) return false;
    if (data[x] > data[y]) swap(x, y);
    data[x] += data[y];
    data[y] = x;
    return true;
  }

  // f ... merge function
  template<typename F>
  int unite(int x, int y,const F &f) {
    if ((x = find(x)) == (y = find(y))) return false;
    if (data[x] > data[y]) swap(x, y);
    data[x] += data[y];
    data[y] = x;
    f(x, y);
    return true;
  }

  int size(int k) { return -data[find(k)]; }

  int same(int x, int y) { return find(x) == find(y); }
};

/**
 * @brief Union Find(Disjoint Set Union)
 * @docs docs/data-structure/union-find.md
 */




template <typename T>
struct edge {
  int src, to;
  T cost;

  edge(int _to, T _cost) : src(-1), to(_to), cost(_cost) {}
  edge(int _src, int _to, T _cost) : src(_src), to(_to), cost(_cost) {}

  edge &operator=(const int &x) {
    to = x;
    return *this;
  }

  operator int() const { return to; }
};
template <typename T>
using Edges = vector<edge<T>>;
template <typename T>
using WeightedGraph = vector<Edges<T>>;
using UnweightedGraph = vector<vector<int>>;

// Input of (Unweighted) Graph
UnweightedGraph graph(int N, int M = -1, bool is_directed = false,
                      bool is_1origin = true) {
  UnweightedGraph g(N);
  if (M == -1) M = N - 1;
  for (int _ = 0; _ < M; _++) {
    int x, y;
    cin >> x >> y;
    if (is_1origin) x--, y--;
    g[x].push_back(y);
    if (!is_directed) g[y].push_back(x);
  }
  return g;
}

// Input of Weighted Graph
template <typename T>
WeightedGraph<T> wgraph(int N, int M = -1, bool is_directed = false,
                        bool is_1origin = true) {
  WeightedGraph<T> g(N);
  if (M == -1) M = N - 1;
  for (int _ = 0; _ < M; _++) {
    int x, y;
    cin >> x >> y;
    T c;
    cin >> c;
    if (is_1origin) x--, y--;
    g[x].emplace_back(x, y, c);
    if (!is_directed) g[y].emplace_back(y, x, c);
  }
  return g;
}

// Input of Edges
template <typename T>
Edges<T> esgraph([[maybe_unused]] int N, int M, int is_weighted = true,
                 bool is_1origin = true) {
  Edges<T> es;
  for (int _ = 0; _ < M; _++) {
    int x, y;
    cin >> x >> y;
    T c;
    if (is_weighted)
      cin >> c;
    else
      c = 1;
    if (is_1origin) x--, y--;
    es.emplace_back(x, y, c);
  }
  return es;
}

// Input of Adjacency Matrix
template <typename T>
vector<vector<T>> adjgraph(int N, int M, T INF, int is_weighted = true,
                           bool is_directed = false, bool is_1origin = true) {
  vector<vector<T>> d(N, vector<T>(N, INF));
  for (int _ = 0; _ < M; _++) {
    int x, y;
    cin >> x >> y;
    T c;
    if (is_weighted)
      cin >> c;
    else
      c = 1;
    if (is_1origin) x--, y--;
    d[x][y] = c;
    if (!is_directed) d[y][x] = c;
  }
  return d;
}

/**
 * @brief グラフテンプレート
 * @docs docs/graph/graph-template.md
 */







using namespace std;

template <typename G>
vector<int> centroid(const G &g) {
  const int N = (int)g.size();

  vector<pair<int, int> > st;
  st.emplace_back(0, -1);
  vector<int> sz(N), par(N);
  while (!st.empty()) {
    auto p = st.back();
    if (sz[p.first] == 0) {
      sz[p.first] = 1;
      for (auto &to : g[p.first])
        if (to != p.second) st.emplace_back(to, p.first);
    } else {
      for (auto &to : g[p.first])
        if (to != p.second) sz[p.first] += sz[to];
      par[p.first] = p.second;
      st.pop_back();
    }
  }

  vector<int> ret;
  int size = N;
  for (int i = 0; i < N; i++) {
    int val = N - sz[i];
    for (auto &to : g[i])
      if (to != par[i]) val = max(val, sz[to]);
    if (val < size) size = val, ret.clear();
    if (val == size) ret.emplace_back(i);
  }

  return ret;
}



using namespace std;



namespace internal {
using i64 = long long;
using u64 = unsigned long long;
using u128 = __uint128_t;

template <int BASE_NUM = 2>
struct Hash : array<u64, BASE_NUM> {
  using array<u64, BASE_NUM>::operator[];
  static constexpr int n = BASE_NUM;

  Hash() : array<u64, BASE_NUM>() {}

  static constexpr u64 md = (1ull << 61) - 1;

  constexpr static Hash set(const i64 &a) {
    Hash res;
    fill(begin(res), end(res), cast(a));
    return res;
  }
  Hash &operator+=(const Hash &r) {
    for (int i = 0; i < n; i++)
      if (((*this)[i] += r[i]) >= md) (*this)[i] -= md;
    return *this;
  }
  Hash &operator+=(const i64 &r) {
    u64 s = cast(r);
    for (int i = 0; i < n; i++)
      if (((*this)[i] += s) >= md) (*this)[i] -= md;
    return *this;
  }
  Hash &operator-=(const Hash &r) {
    for (int i = 0; i < n; i++)
      if (((*this)[i] += md - r[i]) >= md) (*this)[i] -= md;
    return *this;
  }
  Hash &operator-=(const i64 &r) {
    u64 s = cast(r);
    for (int i = 0; i < n; i++)
      if (((*this)[i] += md - s) >= md) (*this)[i] -= md;
    return *this;
  }
  Hash &operator*=(const Hash &r) {
    for (int i = 0; i < n; i++) (*this)[i] = modmul((*this)[i], r[i]);
    return *this;
  }
  Hash &operator*=(const i64 &r) {
    u64 s = cast(r);
    for (int i = 0; i < n; i++) (*this)[i] = modmul((*this)[i], s);
    return *this;
  }

  Hash operator+(const Hash &r) { return Hash(*this) += r; }
  Hash operator+(const i64 &r) { return Hash(*this) += r; }
  Hash operator-(const Hash &r) { return Hash(*this) -= r; }
  Hash operator-(const i64 &r) { return Hash(*this) -= r; }
  Hash operator*(const Hash &r) { return Hash(*this) *= r; }
  Hash operator*(const i64 &r) { return Hash(*this) *= r; }
  Hash operator-() const {
    Hash res;
    for (int i = 0; i < n; i++) res[i] = (*this)[i] == 0 ? 0 : md - (*this)[i];
    return res;
  }
  friend Hash pfma(const Hash &a, const Hash &b, const Hash &c) {
    Hash res;
    for (int i = 0; i < n; i++) res[i] = modfma(a[i], b[i], c[i]);
    return res;
  }
  friend Hash pfma(const Hash &a, const Hash &b, const i64 &c) {
    Hash res;
    u64 s = cast(c);
    for (int i = 0; i < n; i++) res[i] = modfma(a[i], b[i], s);
    return res;
  }

  Hash pow(long long e) {
    Hash a{*this}, res{Hash::set(1)};
    for (; e; a *= a, e >>= 1) {
      if (e & 1) res *= a;
    }
    return res;
  }

  static Hash get_basis() {
    static auto rand_time =
        chrono::duration_cast<chrono::nanoseconds>(
            chrono::high_resolution_clock::now().time_since_epoch())
            .count();
    static mt19937_64 rng(rand_time);
    Hash h;
    for (int i = 0; i < n; i++) {
      while (isPrimitive(h[i] = rng() % (md - 1) + 1) == false)
        ;
    }
    return h;
  }

 private:
  static u64 modpow(u64 a, u64 b) {
    u64 r = 1;
    for (a %= md; b; a = modmul(a, a), b >>= 1) r = modmul(r, a);
    return r;
  }
  static bool isPrimitive(u64 x) {
    for (auto &d : vector<u64>{2, 3, 5, 7, 11, 13, 31, 41, 61, 151, 331, 1321})
      if (modpow(x, (md - 1) / d) <= 1) return false;
    return true;
  }
  static inline constexpr u64 cast(const long long &a) {
    return a < 0 ? a + md : a;
  }
  static inline constexpr u64 modmul(const u64 &a, const u64 &b) { 
    u128 d = u128(a) * b;
    u64 ret = (u64(d) & md) + u64(d >> 61);
    return ret >= md ? ret - md : ret;
  }
  static inline constexpr u64 modfma(const u64 &a, const u64 &b, const u64 &c) {
    u128 d = u128(a) * b + c;
    u64 ret = (d >> 61) + (u64(d) & md);
    return ret >= md ? ret - md : ret;
  }
};

}  // namespace internal

/**
 * @brief ハッシュ構造体
 * @docs docs/internal/internal-hash.md
 */


template <typename G>
struct RootedTreeHash {
  using Hash = internal::Hash<1>;

  const G& g;
  int n;
  vector<Hash> hash;
  vector<int> depth;

  static vector<Hash>& xs() {
    static vector<Hash> _xs;
    return _xs;
  }

  RootedTreeHash(const G& _g, int root = 0) : g(_g), n(g.size()) {
    hash.resize(n);
    depth.resize(n, 0);
    while ((int)xs().size() <= n) xs().push_back(Hash::get_basis());
    dfs(root, -1);
  }

 private:
  int dfs(int c, int p) {
    int dep = 0;
    for (auto& d : g[c]) {
      if (d != p) dep = max(dep, dfs(d, c) + 1);
    }
    Hash x = xs()[dep], h = Hash::set(1);
    for (auto& d : g[c]) {
      if (d != p) h = h * (x + hash[d]);
    }
    hash[c] = h;
    return depth[c] = dep;
  }
};

/**
 * @brief 根付き木のハッシュ
 */


template <typename G>
vector<typename RootedTreeHash<G>::Hash> tree_hash(const G& g) {
  using Hash = typename RootedTreeHash<G>::Hash;
  auto cs = centroid(g);
  if ((int)cs.size() == 1) cs.push_back(cs[0]);
  vector<Hash> hs;
  for (auto& c : cs) hs.push_back(RootedTreeHash{g, c}.hash[c]);
  sort(begin(hs), end(hs));
  return hs;
}


using namespace Nyaan;

void q() {
  ini(N1, M1);
  auto g1 = graph(N1, M1);
  ini(N2);
  auto g2 = graph(N2);

  auto th = tree_hash(g2);

  UnionFind uf(N1);
  rep(i, N1) each(j, g1[i]) uf.unite(i, j);
  ll ans = 0;
  rep(i, N1) {
    if (uf.find(i) != i) continue;
    vvi h(uf.size(i));

    int buf = 0;
    unordered_map<int, int> mp;
    auto dfs = [&](auto rc, int c, int p) -> void {
      mp[c] = buf++;
      each(d, g1[c]) {
        if (d == p) continue;
        rc(rc, d, c);
        h[mp[c]].push_back(mp[d]);
        h[mp[d]].push_back(mp[c]);
      }
    };
    dfs(dfs, i, -1);

    auto th2 = tree_hash(h);
    ans += th == th2;
  }
  out(ans);
}

void Nyaan::solve() {
  int t = 1;
  // in(t);
  while (t--) q();
}
accuracy: 1
time_ms: 890
memory_kb: 87776
final_rank: 18

code_snippet: #include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define ALL(v) v.begin(),v.end()
#define dbg(x) cerr << #x << ": " << (x) << endl;
template<class F, class S>
ostream& operator<<(ostream& os, pair<F,S>& p) {
    os << '(' << p.first << ',' << p.second << ')';
    return os;
}
template<class Iter>
void print(Iter beg, Iter end) {
    for (Iter itr = beg; itr != end; ++itr) {
        cerr << *itr << ' ';
    }
    cerr << '\n';
}

using Graph = vector<vector<int>>;

// O(nlogn)
vector<Graph> split(const Graph& g) {
    int n = g.size();
    vector<vector<int>> mem;
    vector<int> gid(n,-1);
    int cnt = 0;
    for (int i = 0; i < n; ++i) {
        if (gid[i] != -1) continue;
        mem.emplace_back();
        queue<int> q;
        q.push(i);
        gid[i] = cnt;
        mem[cnt].push_back(i);
        while (q.size()) {
            int v = q.front();
            q.pop();
            for (int to : g[v]) {
                if (gid[to] == -1) {
                    gid[to] = cnt;
                    mem[cnt].push_back(to);
                    q.push(to);
                }
            }
        }
        ++cnt;
    }
    vector<Graph> res(cnt);
    for (int i = 0; i < cnt; ++i) {
        sort(ALL(mem[i]));
        res[i].resize(mem[i].size());
    }
    for (int i = 0; i < n; ++i) {
        int u = lower_bound(ALL(mem[gid[i]]), i) - mem[gid[i]].begin();
        for (int to : g[i]) {
            int v = lower_bound(ALL(mem[gid[i]]), to) - mem[gid[i]].begin();
            res[gid[i]][u].push_back(v);
        }
    }
    return res;
}

// O(n+m)
tuple<vector<int>, vector<int>, int> bfs(const Graph& g, int start) {
    int n = g.size();
    vector<int> dis(n, 1e9), pre(n, -1);
    queue<int> q;
    dis[start] = 0;
    q.push(start);
    int ma = 0;
    int farthest = start;
    while (q.size()) {
        int pos = q.front();
        q.pop();
        for (int to : g[pos]) {
            if (dis[pos] + 1 < dis[to]) {
                dis[to] = dis[pos] + 1;
                pre[to] = pos;
                q.push(to);
                if (dis[to] > ma) {
                    ma = dis[to];
                    farthest = to;
                }
            }
        }
    }
    return {dis, pre, farthest};
}

vector<int> center(const Graph& tree) {
    auto [dis1, pre1, v1] = bfs(tree, 0);
    auto [dis2, pre2, v2] = bfs(tree, v1);

    vector<int> path;
    int now = v2;
    while (now != -1) {
        path.push_back(now);
        now = pre2[now];
    }

    vector<int> centers;
    centers.push_back(path[path.size() / 2]);
    if (path.size() % 2 == 0) centers.push_back(path[path.size() / 2 - 1]);
    return centers;
}

int name(int pos, int pre, const Graph& tree, map<vector<int>,int>& cmp, int& id) {
    vector<int> seq;
    for (int c : tree[pos]) {
        if (c == pre) continue;
        seq.push_back(name(c, pos, tree, cmp, id));
    }
    if (seq.empty()) {
        return 0;
    }
    sort(ALL(seq));
    if (cmp.count(seq)) {
        return cmp.at(seq);
    } else {
        cmp[seq] = ++id;
        return id;
    }
}

bool isomorphism_rooted(const Graph& t1, const Graph& t2, int r1, int r2) {
    int id = 0;
    map<vector<int>, int> cmp;
    int name1 = name(r1, -1, t1, cmp, id);
    int name2 = name(r2, -1, t2, cmp, id);
    return (name1 == name2);
}

bool isomorphism(const Graph& t1, const Graph& t2) {
    auto cen1 = center(t1);
    auto cen2 = center(t2);
    if (cen1.size() != cen2.size()) {
        return false;
    }
    for (int c : cen1) {
        if (isomorphism_rooted(t1, t2, c, cen2[0])) return true;
    }
    return false;
}


int N1,M;
Graph G;
int N2;
Graph G2;

int main() {
    ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> N1 >> M;
    G.resize(N1);
    for (int i = 0; i < M; ++i) {
        int a,b;
        cin >> a >> b;
        --a; --b;
        G[a].emplace_back(b);
        G[b].emplace_back(a);
    }
    cin >> N2;
    G2.resize(N2);
    for (int i = 0; i < N2-1; ++i) {
        int a,b;
        cin >> a >> b;
        --a; --b;
        G2[a].emplace_back(b);
        G2[b].emplace_back(a);
    }
    vector<Graph> G1 = split(G);
    auto cen2 = center(G2);
    int id = 0;
    map<vector<int>, int> cmp;
    int name2 = name(cen2[0], -1, G2, cmp, id);
    
    int ans = 0;
    for (auto& g : G1) {
        auto cen1 = center(g);
        if (cen1.size() != cen2.size()) continue;
        int iso = 0;
        for (int c : cen1) {
            int name1 = name(c, -1, g, cmp, id);
            if (name1 == name2) {
                iso = 1;
                break;
            }
        }
        ans += iso;
    }
    cout << ans << '\n';
}
accuracy: 1
time_ms: 460
memory_kb: 77232
final_rank: 11

code_snippet: #line 1 "aoj2821.test.cpp"
#define PROBLEM "https://onlinejudge.u-aizu.ac.jp/problems/2821"
#line 1 "/Users/siro53/kyo-pro/compro_library/template/template.cpp"
#pragma region Macros
#include <bits/stdc++.h>
using namespace std;
template <class T> inline bool chmax(T &a, T b) {
    if(a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <class T> inline bool chmin(T &a, T b) {
    if(a > b) {
        a = b;
        return 1;
    }
    return 0;
}
#ifdef DEBUG
template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << '(' << p.first << ',' << p.second << ')';
    return os;
}
template <class T> ostream &operator<<(ostream &os, const vector<T> &v) {
    os << '{';
    for(int i = 0; i < (int)v.size(); i++) {
        if(i) { os << ','; }
        os << v[i];
    }
    os << '}';
    return os;
}
void debugg() { cerr << endl; }
template <class T, class... Args>
void debugg(const T &x, const Args &... args) {
    cerr << " " << x;
    debugg(args...);
}
#define debug(...)                                                             \
    cerr << __LINE__ << " [" << #__VA_ARGS__ << "]: ", debugg(__VA_ARGS__)
#define dump(x) cerr << __LINE__ << " " << #x << " = " << (x) << endl
#else
#define debug(...) (void(0))
#define dump(x) (void(0))
#endif

struct Setup {
    Setup() {
        cin.tie(0);
        ios::sync_with_stdio(false);
        cout << fixed << setprecision(15);
    }
} __Setup;

using ll = long long;
#define OVERLOAD3(_1, _2, _3, name, ...) name
#define ALL(v) (v).begin(), (v).end()
#define RALL(v) (v).rbegin(), (v).rend()
#define REP1(i, n) for(int i = 0; i < int(n); i++)
#define REP2(i, a, b) for(int i = (a); i < int(b); i++)
#define REP(...) OVERLOAD3(__VA_ARGS__, REP2, REP1)(__VA_ARGS__)
#define UNIQUE(v) sort(ALL(v)), (v).erase(unique(ALL(v)), (v).end())
#define REVERSE(v) reverse(ALL(v))
#define SZ(v) ((int)(v).size())
const int INF = 1 << 30;
const ll LLINF = 1LL << 60;
constexpr int MOD = 1000000007;
constexpr int MOD2 = 998244353;
const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, 1, 0, -1};

void Case(int i) { cout << "Case #" << i << ": "; }
int popcount(int x) { return __builtin_popcount(x); }
ll popcount(ll x) { return __builtin_popcountll(x); }
#pragma endregion Macros
#line 2 "/Users/siro53/kyo-pro/compro_library/graph/tree/tree-hash.hpp"

#line 2 "/Users/siro53/kyo-pro/compro_library/random/rng.hpp"

#line 5 "/Users/siro53/kyo-pro/compro_library/random/rng.hpp"

class RNG32 {
  public:
    RNG32() : mt(std::chrono::steady_clock::now().time_since_epoch().count()) {}
    // [l, r)
    int randint(int l, int r) {
        std::uniform_int_distribution<int> dist(l, r - 1);
        return dist(mt);
    }
    int randint(int r) { return randint(0, r); }

  private:
    std::mt19937 mt;
};

class RNG64 {
  public:
    RNG64() : mt(std::chrono::steady_clock::now().time_since_epoch().count()) {}
    // [l, r)
    long long randint(long long l, long long r) {
        std::uniform_int_distribution<long long> dist(l, r - 1);
        return dist(mt);
    }
    long long randint(long long r) { return randint(0, r); }

  private:
    std::mt19937_64 mt;
};
#line 2 "/Users/siro53/kyo-pro/compro_library/graph/tree/diameter.hpp"

#line 6 "/Users/siro53/kyo-pro/compro_library/graph/tree/diameter.hpp"

#line 2 "/Users/siro53/kyo-pro/compro_library/graph/graph_template.hpp"

#line 5 "/Users/siro53/kyo-pro/compro_library/graph/graph_template.hpp"

template <class Cost = int> struct Edge {
    int from, to;
    Cost cost;
    int id;
    Edge() = default;
    explicit Edge(int from, int to, Cost cost = 1, int id = -1)
        : from(from), to(to), cost(cost), id(id) {}
};

template <class Cost = int> class Graph {
  public:
    Graph() = default;
    explicit Graph(int N) : N(N), M(0), G(N) {}

    inline void add_directed_edge(int from, int to, Cost cost = 1) {
        assert(0 <= from && from < N);
        assert(0 <= to && to < N);
        G[from].emplace_back(from, to, cost, M++);
    }

    inline void add_undirected_edge(int from, int to, Cost cost = 1) {
        assert(0 <= from && from < N);
        assert(0 <= to && to < N);
        G[from].emplace_back(from, to, cost, M);
        G[to].emplace_back(to, from, cost, M++);
    }

    inline size_t size() const { return G.size(); }
    inline std::vector<Edge<Cost>> &operator[](const int &i) { return G[i]; }
    inline const std::vector<Edge<Cost>> &operator[](const int &i) const {
        return G[i];
    }

  private:
    int N, M;
    std::vector<std::vector<Edge<Cost>>> G;
};

template <class Cost = int> using Edges = std::vector<Edge<Cost>>;
#line 8 "/Users/siro53/kyo-pro/compro_library/graph/tree/diameter.hpp"

template <class Cost = int>
std::pair<Cost, std::vector<int>> get_diameter(const Graph<Cost> &G) {
    std::vector<Cost> depth(G.size());
    std::vector<int> par(G.size(), -1);
    auto dfs = [&](auto &&self, int u, int p, Cost d) -> void {
        depth[u] = d;
        par[u] = p;
        for(const auto &e : G[u]) {
            if(e.to == p) continue;
            self(self, e.to, u, d + e.cost);
        }
    };
    dfs(dfs, 0, -1, 0);
    int from = std::max_element(depth.begin(), depth.end()) - depth.begin();
    dfs(dfs, from, -1, 0);
    int to = std::max_element(depth.begin(), depth.end()) - depth.begin();
    std::vector<int> path = {to};
    while(1) {
        int nxt = par[path.back()];
        if(nxt == -1) break;
        path.push_back(nxt);
    }
    return std::make_pair(depth[to], path);
}
#line 5 "/Users/siro53/kyo-pro/compro_library/graph/tree/tree-hash.hpp"

template <class Cost> class TreeHash {
  public:
    explicit TreeHash(int n) : G(n) {
        RNG64 rng;
        for(int i = 0; i < n; i++) rnd.push_back(rng.randint(1, mod - 1));
    }
    explicit TreeHash(const Graph<Cost> &g) : G(g) {
        RNG64 rng;
        for(int i = 0; i < (int)G.size(); i++)
            rnd.push_back(rng.randint(1, mod - 1));
    }
    inline void add_edge(int from, int to, Cost cost = 1) {
        G.add_undirected_edge(from, to, cost);
    }
    unsigned long long get_hash() {
        auto [_, path] = get_diameter(G);
        int len = (int)path.size();
        std::vector<int> cent;
        if(len & 1) {
            cent = {path[len / 2]};
        } else {
            cent = {path[len / 2], path[len / 2 - 1]};
        }
        unsigned long long res = std::numeric_limits<unsigned long long>::max();
        for(const int &root : cent) {
            std::vector<unsigned long long> hashed(G.size(), 1);
            hash_dfs(root, -1, 0, hashed);
            res = std::min(res, hashed[root]);
        }
        return res;
    }

  private:
    Graph<Cost> G;
    static constexpr unsigned long long mod = (1ULL << 61) - 1;
    static inline std::vector<unsigned long long> rnd;

    inline unsigned long long add(unsigned long long a, unsigned long long b) {
        if((a += b) >= mod) a -= mod;
        return a;
    }
    inline unsigned long long mul(unsigned long long a, unsigned long long b) {
        __uint128_t t = a;
        t *= b;
        unsigned long long na = t >> 61;
        unsigned long long nb = t & mod;
        if((na += nb) >= mod) na -= mod;
        return na;
    }
    unsigned long long hash_dfs(int v, int p, int d,
                                std::vector<unsigned long long> &res) {
        for(const auto &e : G[v]) {
            if(e.to != p) {
                res[v] = mul(res[v], hash_dfs(e.to, v, d + 1, res));
            }
        }
        return res[v] = add(res[v], rnd[d]);
    }
};
#line 4 "aoj2821.test.cpp"

int main() {
    int N1, M1, N2;
    cin >> N1 >> M1;
    Graph<int> G1(N1);
    for(int i = 0; i < M1; i++) {
        int a, b;
        cin >> a >> b;
        a--;
        b--;
        G1.add_undirected_edge(a, b);
    }
    cin >> N2;
    TreeHash<int> G2(N2);
    for(int i = 0; i < N2 - 1; i++) {
        int a, b;
        cin >> a >> b;
        a--;
        b--;
        G2.add_edge(a, b);
    }
    auto g2hash = G2.get_hash();

    int ans = 0;
    vector<bool> used(N1, false);

    for(int i = 0; i < N1; i++) {
        if(!used[i]) {
            map<int, int> toid;
            int sz = 0;
            auto dfs1 = [&](auto &&dfs1, int v, int p, int &k) -> void {
                used[v] = true;
                toid[v] = k++;
                for(const auto& e : G1[v]) {
                    if(e.to != p) {
                        dfs1(dfs1, e.to, v, k);
                    }
                }
            };
            dfs1(dfs1, i, -1, sz);
            TreeHash<int> g1(sz);
            auto dfs2 = [&](auto &&dfs2, int v, int p) -> void {
                for(const auto& e : G1[v]) {
                    if(e.to == p) {
                        continue;
                    }
                    g1.add_edge(toid[e.to], toid[v]);
                    dfs2(dfs2, e.to, v);
                }
            };
            dfs2(dfs2, i, -1);
            auto g1hash = g1.get_hash();
            ans += (g1hash == g2hash);
        }
    }

    cout << ans << endl;
}
accuracy: 1
time_ms: 440
memory_kb: 90452
final_rank: 13

code_snippet: #include <bits/stdc++.h>
#pragma GCC optimize("Ofast")
using namespace std;
using std::cout;
using std::cin;
using std::endl;
using ll=long long;
using ld=long double;
ll ILL=2167167167167167167;
const int INF=2100000000;
const ll mod=1e9+7;
#define rep(i,a) for (ll i=0;i<a;i++)
#define all(p) p.begin(),p.end()
template<class T> using _pq = priority_queue<T, vector<T>, greater<T>>;
template<class T> ll LB(vector<T> &v,T a){return lower_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> ll UB(vector<T> &v,T a){return upper_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> bool chmin(T &a,const T &b){if(a>b){a=b;return 1;}else return 0;}
template<class T> bool chmax(T &a,const T &b){if(a<b){a=b;return 1;}else return 0;}
template<class T> void So(vector<T> &v) {sort(v.begin(),v.end());}
template<class T> void Sore(vector<T> &v) {sort(v.begin(),v.end(),[](T x,T y){return x>y;});}
void yneos(bool a){if(a) cout<<"Yes\n"; else cout<<"No\n";}
template<class T> void vec_out(vector<T> &p){for(int i=0;i<(int)(p.size());i++){if(i) cout<<" ";cout<<p[i];}cout<<"\n";}
template<class T> T min(vector<T> &a){assert(!a.empty());T ans=a[0];for(auto &x:a) chmin(ans,x);return ans;}
template<class T> T max(vector<T> &a){assert(!a.empty());T ans=a[0];for(auto &x:a) chmax(ans,x);return ans;}


std::vector<std::vector<int>> tree_in(int N){
	std::vector<std::vector<int>> G(N);
	for(int i=0;i<N-1;i++){
		int a;int b;
		cin>>a>>b; 
		a--,b--;
		G[a].push_back(b);
		G[b].push_back(a);
	}
	return G;
}
std::tuple<std::vector<int>,std::vector<int>,std::vector<int>> tree_order_pare_depth(std::vector<std::vector<int>> &G,int root){
	int n=G.size();
	std::vector<int> order={root},pare(n,-1),depth(n);
	pare[root]=-2;
	for(int i=0;i<n;i++){
		int a=order[i];
		for(auto x:G[a]){
			if(pare[x]==-1){
				pare[x]=a;
				depth[x]=depth[a]+1;
				order.push_back(x);
			}
		}
	}
	return {order,pare,depth};
}
std::vector<int> tree_diameter_path(std::vector<std::vector<int>> &G){
	int n=G.size();
	auto r=(std::get<0>(tree_order_pare_depth(G,0))).at(n-1);
	std::vector<int> order,pare,depth,ans;
	tie(order,pare,depth)=tree_order_pare_depth(G,r);
	int ind=order[n-1];
	while(ind!=-2){
		ans.push_back(ind);
		ind=pare[ind];
	}
	return ans;
}

namespace po167{
struct UFtree
{
	int _n;
	std::vector<int> wei;
	std::vector<int> q;
	int component;
	UFtree(int n):_n(n),wei(n),component(n),par(n){
		for(int i=0;i<n;i++){
			wei[i]=1,par[i]=i;
		}
	}
	void intialize(){
		for(auto x:q){
			wei[root(x)]=1;
			par[x]=x;
		}
		component=(int)par.size();
		q={};
	}
	//根っこを返す
	int root(int a){
		assert(0<=a&&a<_n);
		if(a==par[a]) return a;
		return par[a]=root(par[a]);
	}
	//trueなら1,falseなら0
	int same(int a,int b){
		assert(0<=a&&a<_n);
		assert(0<=b&&b<_n);
		if(root(a)==root(b)) return 1;
		else return 0;
	}
	//a,bが違う根っこの元なら結合する,結合したらtrueを返す
	bool unite(int a,int b){
		a=root(a),b=root(b);
		if(a==b) return false;
		if(wei[a]<wei[b]) std::swap(a,b);
		par[b]=a;
		q.push_back(b);
		wei[a]+=wei[b];
		component--;
		return true;
	}
	private:
	std::vector<int> par;
};
}
using po167::UFtree;


void solve();
// oddloop
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	int t=1;
	//cin>>t;
	rep(i,t) solve();
}

void solve(){
	map<vector<int>,int> m;
	auto f=[&](vector<vector<int>> &_g)->int{
		auto tmp=tree_diameter_path(_g);
		assert((int)(tmp.size())&1);
		int root=tmp[(int)(tmp.size())/2];
		vector<int> order,pare,depth;
		tie(order,pare,depth)=tree_order_pare_depth(_g,root);
		int _n=_g.size();
		vector<int> p(_n);
		for(int i=_n-1;i>=0;i--){
			int a=order[i];
			vector<int> q;
			for(auto x:_g[a]){
				if(pare[x]==a){
					q.push_back(p[x]);
				}
			}
			So(q);
			if(!m.count(q)) m[q]=(int)(m.size());
			p[a]=m[q];
		}
		return p[root];
	};
	int N,M;
	cin>>N>>M;
	vector<vector<int>> G(N+M);
	UFtree T(N+M);
	rep(i,M){
		int a,b;
		cin>>a>>b;
		a--,b--;
		T.unite(a,i+N);
		T.unite(b,i+N);
		G[a].push_back(i+N);
		G[b].push_back(i+N);
		G[i+N].push_back(a);
		G[i+N].push_back(b);
	}
	int N2;
	cin>>N2;
	vector<vector<int>> H(N2*2-1);
	rep(i,N2-1){
		int a,b;
		cin>>a>>b;
		a--,b--;
		H[a].push_back(i+N2);
		H[b].push_back(i+N2);
		H[N2+i].push_back(a);
		H[N2+i].push_back(b);
	}
	int ans=0,base=f(H);
	vector<vector<int>> X(N+M);
	vector<int> rev(N+M);
	rep(i,N+M){
		X[T.root(i)].push_back(i);
	}
	//cout<<base<<endl;
	rep(i,N+M){
		if(X[i].empty()) continue;
		rep(j,(int)(X[i].size())) rev[X[i][j]]=j;
		vector<vector<int>> _g((int)(X[i].size()));
		for(auto x:X[i]) for(auto y:G[x]){
			_g[rev[x]].push_back(rev[y]);
		}
		if(base==f(_g)) ans++;
	}
	cout<<ans<<"\n";
}
accuracy: 1
time_ms: 700
memory_kb: 145708
final_rank: 19

code_snippet: #define PROBLEM "http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2821"

// #include "../../tree/tree_hash.hpp"

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, s, t) for (int i = (int)(s); i < (int)(t); ++i)
#define revrep(i, t, s) for (int i = (int)(t)-1; i >= (int)(s); --i)
#define all(x) begin(x), end(x)
template <typename T>
bool chmax(T& a, const T& b) {
    return a < b ? (a = b, 1) : 0;
}
template <typename T>
bool chmin(T& a, const T& b) {
    return a > b ? (a = b, 1) : 0;
}

std::pair<int, std::vector<int>> tree_diameter(
    const std::vector<std::vector<int>>& G) {
    std::vector<int> to(G.size());

    auto dfs = [&](const auto& dfs, int v, int p) -> std::pair<int, int> {
        std::pair<int, int> ret(0, v);
        for (int c : G[v]) {
            if (c == p) continue;
            auto weight = dfs(dfs, c, v);
            ++weight.first;
            if (ret < weight) {
                ret = weight;
                to[v] = c;
            }
        }
        return ret;
    };

    auto p = dfs(dfs, 0, -1);
    auto q = dfs(dfs, p.second, -1);
    std::vector<int> path;
    int v = p.second;
    while (v != q.second) {
        path.push_back(v);
        v = to[v];
    }
    path.push_back(v);
    return {q.first, path};
}

long long tree_hash(const std::vector<std::vector<int>>& G, int root = -1) {
    static constexpr long long mod = (1LL << 61) - 1;

    static auto add = [&](long long a, long long b) {
        if ((a += b) >= mod) a -= mod;
        return a;
    };

    static auto mul = [&](long long a, long long b) {
        __int128_t c = (__int128_t)a * b;
        return add(c >> 61, c & mod);
    };

    static std::random_device rd;
    static std::mt19937_64 rng(rd());
    static std::uniform_int_distribution<long long> rand(1, mod - 1);
    static std::vector<long long> R;

    auto dfs = [&](auto& dfs, int v, int p, int d) -> long long {
        if ((int)R.size() == d) {
            R.push_back(rand(rng));
        }
        long long res = 1;
        for (int c : G[v]) {
            if (c != p) {
                res = mul(res, dfs(dfs, c, v, d + 1));
            }
        }
        res = add(res, R[d]);
        return res;
    };

    long long res;
    if (root == -1) {
        int d;
        std::vector<int> path;
        std::tie(d, path) = tree_diameter(G);
        res = dfs(dfs, path[d / 2], -1, 0);
        if (d % 2 == 1) {
            res = std::min(res, dfs(dfs, path[d / 2 + 1], -1, 0));
        }
    } else {
        res = dfs(dfs, root, -1, 0);
    }
    return res;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << fixed << setprecision(15);

    int N1, M1;
    cin >> N1 >> M1;
    vector<vector<int>> G1(N1);
    rep(i, 0, M1) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G1[u].push_back(v);
        G1[v].push_back(u);
    }
    int N2;
    cin >> N2;
    vector<vector<int>> G2(N2);
    rep(i, 0, N2 - 1) {
        int w, x;
        cin >> w >> x;
        --w, --x;
        G2[w].push_back(x);
        G2[x].push_back(w);
    }

    ll h2 = tree_hash(G2);

    vector<int> num(N1, -1);
    int ans = 0;
    rep(i, 0, N1) {
        if (num[i] != -1) continue;
        int k = 0;
        vector<vector<int>> G;
        auto dfs = [&](auto& dfs, int v, int p) -> void {
            num[v] = k++;
            G.emplace_back();
            if (p != -1) {
                G[num[p]].push_back(num[v]);
                G[num[v]].push_back(num[p]);
            }
            for (int c : G1[v]) {
                if (c != p) dfs(dfs, c, v);
            }
        };
        dfs(dfs, i, -1);

        if (G.size() == G2.size() && tree_hash(G) == h2) {
            ++ans;
        }
    }
    cout << ans << endl;
}
accuracy: 1
time_ms: 140
memory_kb: 58492
final_rank: 4

code_snippet: #define PROBLEM "http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2821"

// #include "../../tree/tree_hash.hpp"

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, s, t) for (int i = (int)(s); i < (int)(t); ++i)
#define revrep(i, t, s) for (int i = (int)(t)-1; i >= (int)(s); --i)
#define all(x) begin(x), end(x)
template <typename T> bool chmax(T& a, const T& b) { return a < b ? (a = b, 1) : 0; }
template <typename T> bool chmin(T& a, const T& b) { return a > b ? (a = b, 1) : 0; }

std::pair<int, std::vector<int>> tree_diameter(const std::vector<std::vector<int>>& G) {
    std::vector<int> to(G.size());

    auto dfs = [&](const auto& dfs, int v, int p) -> std::pair<int, int> {
        std::pair<int, int> ret(0, v);
        for (int c : G[v]) {
            if (c == p) continue;
            auto weight = dfs(dfs, c, v);
            ++weight.first;
            if (ret < weight) {
                ret = weight;
                to[v] = c;
            }
        }
        return ret;
    };

    auto p = dfs(dfs, 0, -1);
    auto q = dfs(dfs, p.second, -1);
    std::vector<int> path;
    int v = p.second;
    while (v != q.second) {
        path.push_back(v);
        v = to[v];
    }
    path.push_back(v);
    return {q.first, path};
}

class TreeHash {
public:
    TreeHash() : rng(rd()), rand(1, mod - 1) {}

    long long tree_hash(const std::vector<std::vector<int>>& G, int root = -1) {
        auto dfs = [&](auto& dfs, int v, int p, int d) -> long long {
            if ((int) R.size() == d) {
                R.push_back(rand(rng));
            }
            long long res = 1;
            for (int c : G[v]) {
                if (c != p) {
                    res = mul(res, dfs(dfs, c, v, d + 1));
                }
            }
            res = add(res, R[d]);
            return res;
        };

        long long res;
        if (root == -1) {
            int d;
            std::vector<int> path;
            std::tie(d, path) = tree_diameter(G);
            res = dfs(dfs, path[d / 2], -1, 0);
            if (d % 2 == 1) {
                res = std::min(res, dfs(dfs, path[d / 2 + 1], -1, 0));
            }
        } else {
            res = dfs(dfs, root, -1, 0);
        }
        return res;
    }

private:
    static constexpr long long mod = (1LL << 61) - 1;

    static inline long long add(long long a, long long b) {
        if ((a += b) >= mod) a -= mod;
        return a;
    }

    static inline long long mul(long long a, long long b) {
        __int128_t c = (__int128_t) a * b;
        return add(c >> 61, c & mod);
    }

    std::random_device rd;
    std::mt19937_64 rng;
    std::uniform_int_distribution<long long> rand;

    std::vector<long long> R;
};


int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << fixed << setprecision(15);

    int N1, M1;
    cin >> N1 >> M1;
    vector<vector<int>> G1(N1);
    rep(i,0,M1) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G1[u].push_back(v);
        G1[v].push_back(u);
    }
    int N2;
    cin >> N2;
    vector<vector<int>> G2(N2);
    rep(i,0,N2-1) {
        int w, x;
        cin >> w >> x;
        --w, --x;
        G2[w].push_back(x);
        G2[x].push_back(w);
    }

    TreeHash th;
    ll h2 = th.tree_hash(G2);

    vector<int> num(N1, -1);
    int ans = 0;
    rep(i,0,N1) {
        if (num[i] != -1) continue;
        int k = 0;
        vector<vector<int>> G;
        auto dfs = [&](auto& dfs, int v, int p) -> void {
            num[v] = k++;
            G.emplace_back();
            if (p != -1) {
                G[num[p]].push_back(num[v]);
                G[num[v]].push_back(num[p]);
            }
            for (int c : G1[v]) {
                if (c != p) dfs(dfs, c, v);
            }
        };
        dfs(dfs, i, -1);

        if (G.size() == G2.size() && th.tree_hash(G) == h2) {
            ++ans;
        }
    }
    cout << ans << endl;
}
accuracy: 1
time_ms: 130
memory_kb: 58156
final_rank: 3

code_snippet: //#define _GLIBCXX_DEBUG

#include<bits/stdc++.h>
using namespace std;

#define endl '\n'
#define lfs cout<<fixed<<setprecision(10)
#define ALL(a)  (a).begin(),(a).end()
#define ALLR(a)  (a).rbegin(),(a).rend()
#define UNIQUE(a) (a).erase(unique((a).begin(),(a).end()),(a).end())
#define spa << " " <<
#define fi first
#define se second
#define MP make_pair
#define MT make_tuple
#define PB push_back
#define EB emplace_back
#define rep(i,n,m) for(ll i = (n); i < (ll)(m); i++)
#define rrep(i,n,m) for(ll i = (ll)(m) - 1; i >= (ll)(n); i--)
using ll = long long;
using ld = long double;
const ll MOD1 = 1e9+7;
const ll MOD9 = 998244353;
const ll INF = 1e18;
using P = pair<ll, ll>;
template<typename T> using PQ = priority_queue<T>;
template<typename T> using QP = priority_queue<T,vector<T>,greater<T>>;
template<typename T1, typename T2>bool chmin(T1 &a,T2 b){if(a>b){a=b;return true;}else return false;}
template<typename T1, typename T2>bool chmax(T1 &a,T2 b){if(a<b){a=b;return true;}else return false;}
ll median(ll a,ll b, ll c){return a+b+c-max({a,b,c})-min({a,b,c});}
void ans1(bool x){if(x) cout<<"Yes"<<endl;else cout<<"No"<<endl;}
void ans2(bool x){if(x) cout<<"YES"<<endl;else cout<<"NO"<<endl;}
void ans3(bool x){if(x) cout<<"Yay!"<<endl;else cout<<":("<<endl;}
template<typename T1,typename T2>void ans(bool x,T1 y,T2 z){if(x)cout<<y<<endl;else cout<<z<<endl;}  
template<typename T1,typename T2,typename T3>void anss(T1 x,T2 y,T3 z){ans(x!=y,x,z);};  
template<typename T>void debug(const T &v,ll h,ll w,string sv=" "){for(ll i=0;i<h;i++){cout<<v[i][0];for(ll j=1;j<w;j++)cout<<sv<<v[i][j];cout<<endl;}};
template<typename T>void debug(const T &v,ll n,string sv=" "){if(n!=0)cout<<v[0];for(ll i=1;i<n;i++)cout<<sv<<v[i];cout<<endl;};
template<typename T>void debug(const vector<T>&v){debug(v,v.size());}
template<typename T>void debug(const vector<vector<T>>&v){for(auto &vv:v)debug(vv,vv.size());}
template<typename T>void debug(stack<T> st){while(!st.empty()){cout<<st.top()<<" ";st.pop();}cout<<endl;}
template<typename T>void debug(queue<T> st){while(!st.empty()){cout<<st.front()<<" ";st.pop();}cout<<endl;}
template<typename T>void debug(deque<T> st){while(!st.empty()){cout<<st.front()<<" ";st.pop_front();}cout<<endl;}
template<typename T>void debug(PQ<T> st){while(!st.empty()){cout<<st.top()<<" ";st.pop();}cout<<endl;}
template<typename T>void debug(QP<T> st){while(!st.empty()){cout<<st.top()<<" ";st.pop();}cout<<endl;}
template<typename T>void debug(const set<T>&v){for(auto z:v)cout<<z<<" ";cout<<endl;}
template<typename T>void debug(const multiset<T>&v){for(auto z:v)cout<<z<<" ";cout<<endl;}
template<typename T,size_t size>void debug(const array<T, size> &a){for(auto z:a)cout<<z<<" ";cout<<endl;}
template<typename T,typename V>void debug(const map<T,V>&v){for(auto z:v)cout<<"["<<z.first<<"]="<<z.second<<",";cout<<endl;}
template<typename T>vector<vector<T>>vec(ll x, ll y, T w){vector<vector<T>>v(x,vector<T>(y,w));return v;}
ll gcd(ll x,ll y){ll r;while(y!=0&&(r=x%y)!=0){x=y;y=r;}return y==0?x:y;}
vector<ll>dx={1,-1,0,0,1,1,-1,-1};vector<ll>dy={0,0,1,-1,1,-1,1,-1};
template<typename T>vector<T> make_v(size_t a,T b){return vector<T>(a,b);}
template<typename... Ts>auto make_v(size_t a,Ts... ts){return vector<decltype(make_v(ts...))>(a,make_v(ts...));}
template<typename T1, typename T2>ostream &operator<<(ostream &os, const pair<T1, T2>&p){return os << p.first << " " << p.second;}
template<typename T>ostream &operator<<(ostream &os, const vector<T> &v){for(auto &z:v)os << z << " ";cout<<"|"; return os;}
template<typename T>void rearrange(vector<int>&ord, vector<T>&v){
  auto tmp = v;
  for(int i=0;i<tmp.size();i++)v[i] = tmp[ord[i]];
}
template<typename Head, typename... Tail>void rearrange(vector<int>&ord,Head&& head, Tail&&... tail){
  rearrange(ord, head);
  rearrange(ord, tail...);
}
template<typename T> vector<int> ascend(const vector<T>&v){
  vector<int>ord(v.size());iota(ord.begin(),ord.end(),0);
  sort(ord.begin(),ord.end(),[&](int i,int j){return v[i]<v[j];});
  return ord;
}
template<typename T> vector<int> descend(const vector<T>&v){
  vector<int>ord(v.size());iota(ord.begin(),ord.end(),0);
  sort(ord.begin(),ord.end(),[&](int i,int j){return v[i]>v[j];});
  return ord;
}
ll FLOOR(ll n,ll div){assert(div>0);return n>=0?n/div:(n-div+1)/div;}
ll CEIL(ll n,ll div){assert(div>0);return n>=0?(n+div-1)/div:n/div;}
ll digitsum(ll n){ll ret=0;while(n){ret+=n%10;n/=10;}return ret;}
ll modulo(ll n,ll d){return (n%d+d)%d;};
template<typename T>T min(const vector<T>&v){return *min_element(v.begin(),v.end());}
template<typename T>T max(const vector<T>&v){return *max_element(v.begin(),v.end());}
template<typename T>T acc(const vector<T>&v){return accumulate(v.begin(),v.end(),T(0));};
template<typename T>T reverse(const T &v){return T(v.rbegin(),v.rend());};
//mt19937 mt(chrono::steady_clock::now().time_since_epoch().count());
int popcount(ll x){return __builtin_popcountll(x);};
int poplow(ll x){return __builtin_ctzll(x);};
int pophigh(ll x){return 63 - __builtin_clzll(x);};
template<typename T>T poll(queue<T> &q){auto ret=q.front();q.pop();return ret;};
template<typename T>T poll(priority_queue<T> &q){auto ret=q.top();q.pop();return ret;};
template<typename T>T poll(QP<T> &q){auto ret=q.top();q.pop();return ret;};
template<typename T>T poll(stack<T> &s){auto ret=s.top();s.pop();return ret;};
ll MULT(ll x,ll y){if(LLONG_MAX/x<=y)return LLONG_MAX;return x*y;}
ll POW2(ll x, ll k){ll ret=1,mul=x;while(k){if(mul==LLONG_MAX)return LLONG_MAX;if(k&1)ret=MULT(ret,mul);mul=MULT(mul,mul);k>>=1;}return ret;}
ll POW(ll x, ll k){ll ret=1;for(int i=0;i<k;i++){if(LLONG_MAX/x<=ret)return LLONG_MAX;ret*=x;}return ret;}
template< typename T = int >
struct edge {
  int to;
  T cost;
  int id;
  edge():id(-1){};
  edge(int to, T cost = 1, int id = -1):to(to), cost(cost), id(id){}
  operator int() const { return to; }
};

template<typename T>
using Graph = vector<vector<edge<T>>>;
template<typename T>
Graph<T>revgraph(const Graph<T> &g){
  Graph<T>ret(g.size());
  for(int i=0;i<g.size();i++){
    for(auto e:g[i]){
      int to = e.to;
      e.to = i;
      ret[to].push_back(e);
    }
  }
  return ret;
}
template<typename T>
Graph<T> readGraph(int n,int m,int indexed=1,bool directed=false,bool weighted=false){
  Graph<T> ret(n);
  for(int es = 0; es < m; es++){
    int u,v;
    T w=1;
    cin>>u>>v;u-=indexed,v-=indexed;
    if(weighted)cin>>w;
    ret[u].emplace_back(v,w,es);
    if(!directed)ret[v].emplace_back(u,w,es);
  }
  return ret;
}
template<typename T>
Graph<T> readParent(int n,int indexed=1,bool directed=true){
  Graph<T>ret(n);
  for(int i=1;i<n;i++){
    int p;cin>>p;
    p-=indexed;
    ret[p].emplace_back(i);
    if(!directed)ret[i].emplace_back(p);
  }
  return ret;
}

using ul = unsigned long long;
struct ModintHash{
  static const ul mod = (1LL<<61) - 1;
  static const ul add = mod * 4;
  ul x;
  ModintHash() : x(0) {}
  ModintHash(ul y) : x(calcmod(y)) {}

  ModintHash &operator+=(const ModintHash &p) {
    if((x += p.x) >= mod) x -= mod;
    return *this;
  }

  ModintHash &operator*=(const ModintHash &p) {
    x = mul(x,p.x);
    return *this;
  }

  friend ModintHash operator+(const ModintHash& lhs, const ModintHash& rhs) {
        return ModintHash(lhs) += rhs;
  }

  friend ModintHash operator*(const ModintHash& lhs, const ModintHash& rhs) {
        return ModintHash(lhs) *= rhs;
  }

  bool operator==(const ModintHash &p) const { return x == p.x; }

  bool operator!=(const ModintHash &p) const { return x != p.x; }

  friend ostream &operator<<(ostream &os, const ModintHash &p) {
    return os << p.x;
  }

  ul mul(ul a, ul b){
    ul au = a >> 31;
    ul ad = a & (1UL << 31) - 1;
    ul bu = b >> 31;
    ul bd = b & (1UL << 31) - 1;
    ul mid = ad * bu + au * bd;
    ul midu = mid >> 30;
    ul midd = mid & (1UL << 30) - 1;
    return calcmod(au * bu * 2 + midu + (midd << 31) + ad * bd);
  }
  ul calcmod(ul val){
    val = (val & mod) + (val >> 61);
    if(val >= mod) val -= mod;
    return val;
  }
};

struct TreeHash{
  using H = ModintHash;
  mt19937_64 mt;
  vector<H>h;
  TreeHash(int max_n):h(max_n+1),mt(chrono::steady_clock::now().time_since_epoch().count()){
    uniform_int_distribution<ll>dist(1,(1LL<<61)-1);
    for(int i=0;i<=max_n;i++){
      h[i]=H(dist(mt));
    }
  }
  template<typename T>
  H hashing_tree(const Graph<T>&g){
    auto cents=centroid_enum(g);
    H ret=1;
    for(auto c:cents){
      ret*=h[0]+hashing(g,c);
    }
    return ret;
  }
  template<typename T>
  H hashing(const Graph<T>&g,int root){
    return dfs_hash(g,root,-1,1);
  }
  template<typename T>
  H dfs_hash(const Graph<T>&g,int v,int par,int d){
    H ret = 1;
    for(auto to:g[v]){
      if(to==par)continue;
      ret *= (dfs_hash(g,to,v,d+1) + h[d]);
    }
    return ret;
  }
  vector<int>sub;
  vector<int>centroids;
  template<typename T>
  vector<int>centroid_enum(const Graph<T>&g){
    sub.assign(g.size(),1);
    centroids.clear();
    dfs_sz(g,0,-1);
    return centroids;
  }
  template<typename T>
  void dfs_sz(const Graph<T>&g, int v,int par){
    bool check=true;
    for(auto to:g[v]){
      if(to==par)continue;
      dfs_sz(g,to,v);
      sub[v]+=sub[to];
      if(sub[to]*2>g.size())check=false;
    }
    if(sub[v]*2<g.size())check=false;
    if(check)centroids.push_back(v);
  }
};


//無向グラフの連結成分分解
template<typename T>
struct Components{
  const Graph<T>&g;
  vector<Graph<T>>gs;
  vector<vector<int>>id;//[連結成分番号][その成分での頂点番号]=元の頂点番号
  vector<pair<int,int>>pos;//[頂点番号]=(連結成分番号,その成分での頂点番号)
  Components(const Graph<T>&g):g(g){
    pos.assign(g.size(),make_pair(-1,-1));
    for(int i=0;i<g.size();i++){
      if(pos[i].fi == -1){
        gs.emplace_back();
        id.emplace_back();
        dfs_build(i,gs.size()-1);
      }
    }
  }
  void dfs_build(int v,int comp_number){
    pos[v]=make_pair(comp_number,id[comp_number].size());
    id[comp_number].push_back(v);
    gs[comp_number].emplace_back();
    for(auto e:g[v]){
      if(pos[e.to].fi==-1){
        dfs_build(e.to,comp_number);
      }
      e.to=pos[e.to].se;
      gs[comp_number][pos[v].se].push_back(e);
    }
  }
};
int main(){
  cin.tie(nullptr);
  ios_base::sync_with_stdio(false);
  ll res=0,buf=0;
  bool judge = true;
  ll n,m;cin>>n>>m;
  auto g1=readGraph<int>(n,m);
  ll tn;cin>>tn;
  auto g2=readGraph<int>(tn,tn-1);
  TreeHash th(300005);
  auto h=th.hashing_tree(g2);
  Components c(g1);
  rep(i,0,c.gs.size()){
    auto thh=th.hashing_tree(c.gs[i]);
    if(h==thh)res++;
  }
  cout<<res<<endl;
  return 0;
}
accuracy: 1
time_ms: 180
memory_kb: 71608
final_rank: 5

code_snippet: #include <bits/stdc++.h>
//#include <atcoder/all>
//using namespace atcoder;
#pragma GCC target ("avx2")
#pragma GCC optimization ("O3")
#pragma GCC optimization ("unroll-loops")
using namespace std;
 
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<string> VS;
typedef pair<int, int> PII;
typedef pair<int, int> pii;
typedef pair<long long, long long> PLL;
typedef pair<int, PII> TIII;
 
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
 
 
#define FOR(i, s, n) for (int i = s; i < (int)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define rep(i, a, b) for (int i = a; i < (b); ++i)
#define trav(a, x) for (auto &a : x)
#define all(x) x.begin(), x.end()
 
#define MOD 1000000007
 
template<class T1, class T2> inline bool chmax(T1 &a, T2 b) {if (a < b) {a = b; return true;} return false;}
template<class T1, class T2> inline bool chmin(T1 &a, T2 b) {if (a > b) {a = b; return true;} return false;}
const double EPS = 1e-8, PI = acos(-1);
const double pi = 3.141592653589793238462643383279;
//ここから編集    
typedef string::const_iterator State;
ll GCD(ll a, ll b){
  return (b==0)?a:GCD(b, a%b);
}
ll LCM(ll a, ll b){
  return a/GCD(a, b) * b;
}
template< int mod >
struct ModInt {
  int x;
 
  ModInt() : x(0) {}
 
  ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}
 
  ModInt &operator+=(const ModInt &p) {
    if((x += p.x) >= mod) x -= mod;
    return *this;
  }
 
  ModInt &operator-=(const ModInt &p) {
    if((x += mod - p.x) >= mod) x -= mod;
    return *this;
  }
 
  ModInt &operator*=(const ModInt &p) {
    x = (int) (1LL * x * p.x % mod);
    return *this;
  }
 
  ModInt &operator/=(const ModInt &p) {
    *this *= p.inverse();
    return *this;
  }
 
  ModInt operator-() const { return ModInt(-x); }
 
  ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }
 
  ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }
 
  ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }
 
  ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }
 
  bool operator==(const ModInt &p) const { return x == p.x; }
 
  bool operator!=(const ModInt &p) const { return x != p.x; }
 
  ModInt inverse() const {
    int a = x, b = mod, u = 1, v = 0, t;
    while(b > 0) {
      t = a / b;
      swap(a -= t * b, b);
      swap(u -= t * v, v);
    }
    return ModInt(u);
  }
 
  ModInt pow(int64_t n) const {
    ModInt ret(1), mul(x);
    while(n > 0) {
      if(n & 1) ret *= mul;
      mul *= mul;
      n >>= 1;
    }
    return ret;
  }
 
  friend ostream &operator<<(ostream &os, const ModInt &p) {
    return os << p.x;
  }
 
  friend istream &operator>>(istream &is, ModInt &a) {
    int64_t t;
    is >> t;
    a = ModInt< mod >(t);
    return (is);
  }
 
  static int get_mod() { return mod; }
};
 
using modint = ModInt< 1000000007 >;
template< typename T >
struct Combination {
  vector< T > _fact, _rfact, _inv;
 
  Combination(int sz) : _fact(sz + 1), _rfact(sz + 1), _inv(sz + 1) {
    _fact[0] = _rfact[sz] = _inv[0] = 1;
    for(int i = 1; i <= sz; i++) _fact[i] = _fact[i - 1] * i;
    _rfact[sz] /= _fact[sz];
    for(int i = sz - 1; i >= 0; i--) _rfact[i] = _rfact[i + 1] * (i + 1);
    for(int i = 1; i <= sz; i++) _inv[i] = _rfact[i] * _fact[i - 1];
  }
 
  inline T fact(int k) const { return _fact[k]; }
 
  inline T rfact(int k) const { return _rfact[k]; }
 
  inline T inv(int k) const { return _inv[k]; }
 
  T P(int n, int r) const {
    if(r < 0 || n < r) return 0;
    return fact(n) * rfact(n - r);
  }
 
  T C(int p, int q) const {
    if(q < 0 || p < q) return 0;
    return fact(p) * rfact(q) * rfact(p - q);
  }
 
  T H(int n, int r) const {
    if(n < 0 || r < 0) return (0);
    return r == 0 ? 1 : C(n + r - 1, r);
  }
};
 
ll modpow(ll x, ll n, ll mod) {
  ll res = 1;
  x %= mod;
  if(x == 0) return 0;
  while(n) {
    if(n&1) res = (res * x) % mod;
    x = (x * x) % mod;
    n >>= 1;
  }
  return res;
} 
inline long long mod(long long a, long long m) {
    return (a % m + m) % m;
}
template<typename T> 
struct BIT{
  int N;
  std::vector<T> node;
  BIT(){}
  BIT(int n){
    N = n;
    node.resize(N+10);
  }
  void build(int n) {
    N = n;
    node.resize(N+10);
  }
  /* a: 1-indexed */
  void add(int a, T x){
    for(int i=a; i<(int)node.size(); i += i & -i) node[i] += x;
  }

  /* [1, a] */
  T sum(int a){
    T res = 0;
    for(int x=a; x>0; x -= x & -x) res += node[x];
    return res;
  }

  /* [l, r] */
  T rangesum(int l, int r){
    return sum(r) - sum(l-1);
  }

  /* 
    a1+a2+...+aw >= valとなるような最小のwを返す
    @verify https://codeforces.com/contest/992/problem/E
  */
  int lower_bound(T val) {
    if(val < 0) return 0;

    int res = 0;
    int n = 1; 
    while (n < N) n *= 2;

    T tv=0;
    for (int k = n; k > 0; k /= 2) {
      if(res + k < N && node[res + k] < val){
        val -= node[res+k];
        res += k;
      }
    }
    return res+1; 
  }
};
struct UnionFind{
  std::vector<int> par;
  std::vector<int> siz;

  UnionFind(int sz_): par(sz_), siz(sz_) {
    for(int i=0; i<sz_; ++i) par[i] = i, siz[i] = 1;
  }

  void init(int sz_){
    par.resize(sz_);
    siz.resize(sz_);
    for(int i=0; i<sz_; ++i) par[i] = i, siz[i] = 1;
  }

  int root(int x){
    if(x == par[x]) return x;
    return par[x] = root(par[x]);
  }

  bool merge(int x, int y){
    x = root(x), y = root(y);
    if(x == y) return false;
    if(siz[x] < siz[y]) std::swap(x, y);
    siz[x] += siz[y];
    par[y] = x;
    return true;
  }

  bool issame(int x, int y){
    return root(x) == root(y);
  }

  int size(int x){
    return siz[root(x)];
  }
};
struct RollingHash{

    using ull = unsigned long long;
    const ull mod = (1ULL << 61) - 1;
    const ull MASK30 = (1ULL << 30) - 1;
    const ull MASK31 = (1ULL << 31) - 1;

    const ull MASK61 = mod;

    ull base;
    int n;
    vector<ull> hash, pow;

    RollingHash(const string &s)
    {
        random_device rnd;
        mt19937_64 mt(rnd());
        base = 1001;
        
        n = (int)s.size();
        hash.assign(n+1, 0);
        pow.assign(n+1, 1);
        
        for(int i=0; i<n; i++){
            hash[i+1] = calc_mod(mul(hash[i], base) + s[i]);
            pow[i+1] = calc_mod(mul(pow[i], base));
        }
    }

    ull calc_mod(ull x){
        ull xu = x >> 61;
        ull xd = x & MASK61;
        ull res = xu + xd;
        if(res >= mod) res -= mod;
        return res;
    }

    ull mul(ull a, ull b){
        ull au = a >> 31;
        ull ad = a & MASK31;
        ull bu = b >> 31;
        ull bd = b & MASK31;
        ull mid = ad * bu + au * bd;
        ull midu = mid >> 30;
        ull midd = mid & MASK30;
        return calc_mod(au * bu * 2 + midu + (midd << 31) + ad * bd);
    }

    //[l,r)のハッシュ値
    inline ull get(int l, int r){
        ull res = calc_mod(hash[r] + mod*3-mul(hash[l], pow[r-l]));
        return res;
    }
    //string tのハッシュ値
    inline ull get(string t){
        ull res = 0;
        for(int i=0; i<t.size(); i++){
            res = calc_mod(mul(res, base)+t[i]);
        }
        return res;
    }
    //string s中のtの数をカウント
    inline int count(string t) {
        if(t.size() > n) return 0;
        auto hs = get(t);
        int res = 0;
        for(int i=0; i<n-t.size()+1; i++){
            if(get(i, i+t.size()) == hs) res++; 
        }
        return res;
    }

    /* 
        concat 
        @verify https://codeforces.com/problemset/problem/514/C
    */
    inline ull concat(ull h1, ull h2, int h2len){
      return calc_mod(h2 + mul(h1, pow[h2len]));
    }

    // LCPを求める S[a:] T[b:]
    inline int LCP(int a, int b){
        int len = min((int)hash.size()-a, (int)hash.size()-b);
        
        int lb = -1, ub = len;
        while(ub-lb>1){
            int mid = (lb+ub)/2;

            if(get(a, a+mid) == get(b, b+mid)) lb = mid;
            else ub = mid;
        }
        return lb;
    }
};
vector<int> centroid(vector<vector<int>> &g) {
  int n = g.size();
  vector<int> centroid;
  vector<int> sz(n);
  vector<bool> visited(n);
  vector<int> res;
  int t = 0;
  function<void (int, int, int)> dfs = [&](int u, int p, int f) {
    sz[u] = 1;
    if(f == 0) {
      for(auto v: g[u]) {
        if(v == p) continue;
        dfs(v, u, f);
        sz[u] += sz[v];
      }
      t = max(t, sz[u]);
    }else{
      visited[u] = true;
      bool is_centroid = true;
      for(auto v: g[u]) {
        if(v == p) continue;
        dfs(v, u, f);
        sz[u] += sz[v];
        if(sz[v] > t/2) is_centroid = false;
      }
      if(t - sz[u] > t/2) is_centroid = false;
      if(is_centroid) centroid.push_back(u);
    }
  };
  REP(i,n) {
    if(visited[i]) continue;
    t = 0;
    dfs(i, -1, 0);
    dfs(i, -1, 1);
    res.push_back(centroid.back());
  }
  return res;
}

ll dfs2(int u, int p, vector<vector<int>>&g, ll mod, ll base) {
  ll ret = base;
  for(auto v: g[u]) {
    if(v == p) continue;
    ll t = dfs2(v, u, g, mod, base);
    ret += (t*t % mod * t)%mod;
    ret %= mod;
  }
  return ret;
}
int main() {  
  cin.tie(0);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(12);

  int n1, m1; cin >> n1 >> m1;
  vector<vector<int>> g(n1);
  unordered_map<ll,ll> memo;
  REP(i,m1) {
    int a, b; cin >> a >> b;
    a--; b--;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  auto c = centroid(g);
  ll mod = 1e9+7;
  random_device rnd;
  mt19937_64 mt(rnd());
  ll base = mt() % mod;
  for(auto root: c) {
    memo[dfs2(root, -1, g, mod, base)]++;
  }

  int n2; cin >> n2;
  vector<vector<int>> g2(n2);
  REP(i,n2-1) {
    int a, b; cin >> a >> b;
    a--; b--;
    g2[a].push_back(b);
    g2[b].push_back(a);
  }
  auto c2 = centroid(g2);
  ll ans = 0;
  
  for(auto root: c2) {
    ans += memo[dfs2(root, -1, g2, mod, base)];
  }
  cout << ans << endl;
  return 0;
}
accuracy: 1
time_ms: 170
memory_kb: 40200
final_rank: 2

code_snippet: #include <bits/stdc++.h>
//#include <atcoder/all>
//using namespace atcoder;
#pragma GCC target ("avx2")
#pragma GCC optimization ("O3")
#pragma GCC optimization ("unroll-loops")
using namespace std;
 
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<string> VS;
typedef pair<int, int> PII;
typedef pair<int, int> pii;
typedef pair<long long, long long> PLL;
typedef pair<int, PII> TIII;
 
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
 
 
#define FOR(i, s, n) for (int i = s; i < (int)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define rep(i, a, b) for (int i = a; i < (b); ++i)
#define trav(a, x) for (auto &a : x)
#define all(x) x.begin(), x.end()
 
#define MOD 1000000007
 
template<class T1, class T2> inline bool chmax(T1 &a, T2 b) {if (a < b) {a = b; return true;} return false;}
template<class T1, class T2> inline bool chmin(T1 &a, T2 b) {if (a > b) {a = b; return true;} return false;}
const double EPS = 1e-8, PI = acos(-1);
const double pi = 3.141592653589793238462643383279;
//ここから編集    
typedef string::const_iterator State;
ll GCD(ll a, ll b){
  return (b==0)?a:GCD(b, a%b);
}
ll LCM(ll a, ll b){
  return a/GCD(a, b) * b;
}
template< int mod >
struct ModInt {
  int x;
 
  ModInt() : x(0) {}
 
  ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}
 
  ModInt &operator+=(const ModInt &p) {
    if((x += p.x) >= mod) x -= mod;
    return *this;
  }
 
  ModInt &operator-=(const ModInt &p) {
    if((x += mod - p.x) >= mod) x -= mod;
    return *this;
  }
 
  ModInt &operator*=(const ModInt &p) {
    x = (int) (1LL * x * p.x % mod);
    return *this;
  }
 
  ModInt &operator/=(const ModInt &p) {
    *this *= p.inverse();
    return *this;
  }
 
  ModInt operator-() const { return ModInt(-x); }
 
  ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }
 
  ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }
 
  ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }
 
  ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }
 
  bool operator==(const ModInt &p) const { return x == p.x; }
 
  bool operator!=(const ModInt &p) const { return x != p.x; }
 
  ModInt inverse() const {
    int a = x, b = mod, u = 1, v = 0, t;
    while(b > 0) {
      t = a / b;
      swap(a -= t * b, b);
      swap(u -= t * v, v);
    }
    return ModInt(u);
  }
 
  ModInt pow(int64_t n) const {
    ModInt ret(1), mul(x);
    while(n > 0) {
      if(n & 1) ret *= mul;
      mul *= mul;
      n >>= 1;
    }
    return ret;
  }
 
  friend ostream &operator<<(ostream &os, const ModInt &p) {
    return os << p.x;
  }
 
  friend istream &operator>>(istream &is, ModInt &a) {
    int64_t t;
    is >> t;
    a = ModInt< mod >(t);
    return (is);
  }
 
  static int get_mod() { return mod; }
};
 
using modint = ModInt< 1000000007 >;
template< typename T >
struct Combination {
  vector< T > _fact, _rfact, _inv;
 
  Combination(int sz) : _fact(sz + 1), _rfact(sz + 1), _inv(sz + 1) {
    _fact[0] = _rfact[sz] = _inv[0] = 1;
    for(int i = 1; i <= sz; i++) _fact[i] = _fact[i - 1] * i;
    _rfact[sz] /= _fact[sz];
    for(int i = sz - 1; i >= 0; i--) _rfact[i] = _rfact[i + 1] * (i + 1);
    for(int i = 1; i <= sz; i++) _inv[i] = _rfact[i] * _fact[i - 1];
  }
 
  inline T fact(int k) const { return _fact[k]; }
 
  inline T rfact(int k) const { return _rfact[k]; }
 
  inline T inv(int k) const { return _inv[k]; }
 
  T P(int n, int r) const {
    if(r < 0 || n < r) return 0;
    return fact(n) * rfact(n - r);
  }
 
  T C(int p, int q) const {
    if(q < 0 || p < q) return 0;
    return fact(p) * rfact(q) * rfact(p - q);
  }
 
  T H(int n, int r) const {
    if(n < 0 || r < 0) return (0);
    return r == 0 ? 1 : C(n + r - 1, r);
  }
};
 
ll modpow(ll x, ll n, ll mod) {
  ll res = 1;
  x %= mod;
  if(x == 0) return 0;
  while(n) {
    if(n&1) res = (res * x) % mod;
    x = (x * x) % mod;
    n >>= 1;
  }
  return res;
} 
inline long long mod(long long a, long long m) {
    return (a % m + m) % m;
}
template<typename T> 
struct BIT{
  int N;
  std::vector<T> node;
  BIT(){}
  BIT(int n){
    N = n;
    node.resize(N+10);
  }
  void build(int n) {
    N = n;
    node.resize(N+10);
  }
  /* a: 1-indexed */
  void add(int a, T x){
    for(int i=a; i<(int)node.size(); i += i & -i) node[i] += x;
  }

  /* [1, a] */
  T sum(int a){
    T res = 0;
    for(int x=a; x>0; x -= x & -x) res += node[x];
    return res;
  }

  /* [l, r] */
  T rangesum(int l, int r){
    return sum(r) - sum(l-1);
  }

  /* 
    a1+a2+...+aw >= valとなるような最小のwを返す
    @verify https://codeforces.com/contest/992/problem/E
  */
  int lower_bound(T val) {
    if(val < 0) return 0;

    int res = 0;
    int n = 1; 
    while (n < N) n *= 2;

    T tv=0;
    for (int k = n; k > 0; k /= 2) {
      if(res + k < N && node[res + k] < val){
        val -= node[res+k];
        res += k;
      }
    }
    return res+1; 
  }
};
struct UnionFind{
  std::vector<int> par;
  std::vector<int> siz;

  UnionFind(int sz_): par(sz_), siz(sz_) {
    for(int i=0; i<sz_; ++i) par[i] = i, siz[i] = 1;
  }

  void init(int sz_){
    par.resize(sz_);
    siz.resize(sz_);
    for(int i=0; i<sz_; ++i) par[i] = i, siz[i] = 1;
  }

  int root(int x){
    if(x == par[x]) return x;
    return par[x] = root(par[x]);
  }

  bool merge(int x, int y){
    x = root(x), y = root(y);
    if(x == y) return false;
    if(siz[x] < siz[y]) std::swap(x, y);
    siz[x] += siz[y];
    par[y] = x;
    return true;
  }

  bool issame(int x, int y){
    return root(x) == root(y);
  }

  int size(int x){
    return siz[root(x)];
  }
};
struct RollingHash{

    using ull = unsigned long long;
    const ull mod = (1ULL << 61) - 1;
    const ull MASK30 = (1ULL << 30) - 1;
    const ull MASK31 = (1ULL << 31) - 1;

    const ull MASK61 = mod;

    ull base;
    int n;
    vector<ull> hash, pow;

    RollingHash(const string &s)
    {
        random_device rnd;
        mt19937_64 mt(rnd());
        base = 1001;
        
        n = (int)s.size();
        hash.assign(n+1, 0);
        pow.assign(n+1, 1);
        
        for(int i=0; i<n; i++){
            hash[i+1] = calc_mod(mul(hash[i], base) + s[i]);
            pow[i+1] = calc_mod(mul(pow[i], base));
        }
    }

    ull calc_mod(ull x){
        ull xu = x >> 61;
        ull xd = x & MASK61;
        ull res = xu + xd;
        if(res >= mod) res -= mod;
        return res;
    }

    ull mul(ull a, ull b){
        ull au = a >> 31;
        ull ad = a & MASK31;
        ull bu = b >> 31;
        ull bd = b & MASK31;
        ull mid = ad * bu + au * bd;
        ull midu = mid >> 30;
        ull midd = mid & MASK30;
        return calc_mod(au * bu * 2 + midu + (midd << 31) + ad * bd);
    }

    //[l,r)のハッシュ値
    inline ull get(int l, int r){
        ull res = calc_mod(hash[r] + mod*3-mul(hash[l], pow[r-l]));
        return res;
    }
    //string tのハッシュ値
    inline ull get(string t){
        ull res = 0;
        for(int i=0; i<t.size(); i++){
            res = calc_mod(mul(res, base)+t[i]);
        }
        return res;
    }
    //string s中のtの数をカウント
    inline int count(string t) {
        if(t.size() > n) return 0;
        auto hs = get(t);
        int res = 0;
        for(int i=0; i<n-t.size()+1; i++){
            if(get(i, i+t.size()) == hs) res++; 
        }
        return res;
    }

    /* 
        concat 
        @verify https://codeforces.com/problemset/problem/514/C
    */
    inline ull concat(ull h1, ull h2, int h2len){
      return calc_mod(h2 + mul(h1, pow[h2len]));
    }

    // LCPを求める S[a:] T[b:]
    inline int LCP(int a, int b){
        int len = min((int)hash.size()-a, (int)hash.size()-b);
        
        int lb = -1, ub = len;
        while(ub-lb>1){
            int mid = (lb+ub)/2;

            if(get(a, a+mid) == get(b, b+mid)) lb = mid;
            else ub = mid;
        }
        return lb;
    }
};
vector<int> centroid(vector<vector<int>> &g) {
  int n = g.size();
  vector<int> centroid;
  vector<int> sz(n);
  vector<bool> visited(n);
  vector<int> res;
  int t = 0;
  function<void (int, int, int)> dfs = [&](int u, int p, int f) {
    sz[u] = 1;
    if(f == 0) {
      for(auto v: g[u]) {
        if(v == p) continue;
        dfs(v, u, f);
        sz[u] += sz[v];
      }
      t = max(t, sz[u]);
    }else{
      visited[u] = true;
      bool is_centroid = true;
      for(auto v: g[u]) {
        if(v == p) continue;
        dfs(v, u, f);
        sz[u] += sz[v];
        if(sz[v] > t/2) is_centroid = false;
      }
      if(t - sz[u] > t/2) is_centroid = false;
      if(is_centroid) centroid.push_back(u);
    }
  };
  REP(i,n) {
    if(visited[i]) continue;
    t = 0;
    dfs(i, -1, 0);
    dfs(i, -1, 1);
    res.push_back(centroid.back());
  }
  return res;
}

ll dfs2(int u, int p, vector<vector<int>>&g, ll mod, ll base) {
  ll ret = base;
  for(auto v: g[u]) {
    if(v == p) continue;
    ll t = dfs2(v, u, g, mod, base);
    ret += t*t % mod;
    ret %= mod;
  }
  return ret;
}
int main() {  
  cin.tie(0);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(12);

  int n1, m1; cin >> n1 >> m1;
  vector<vector<int>> g(n1);
  unordered_map<ll,ll> memo;
  REP(i,m1) {
    int a, b; cin >> a >> b;
    a--; b--;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  auto c = centroid(g);
  ll mod = 1e9+7;
  random_device rnd;
  mt19937_64 mt(rnd());
  ll base = mt() % mod;
  for(auto root: c) {
    memo[dfs2(root, -1, g, mod, base)]++;
  }

  int n2; cin >> n2;
  vector<vector<int>> g2(n2);
  REP(i,n2-1) {
    int a, b; cin >> a >> b;
    a--; b--;
    g2[a].push_back(b);
    g2[b].push_back(a);
  }
  auto c2 = centroid(g2);
  ll ans = 0;
  
  for(auto root: c2) {
    ans += memo[dfs2(root, -1, g2, mod, base)];
  }
  cout << ans << endl;
  return 0;
}
accuracy: 0.6290322580645161
time_ms: 170
memory_kb: 40056
final_rank: 20

code_snippet: #include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef unsigned long long int ull;
#define BIG_NUM 2000000000
#define HUGE_NUM 4000000000000000000 //オーバーフローに注意
#define MOD 1000000007
#define EPS 0.000000001




#define SIZE 300005

struct State{
	State(){
		node = sum_dist = 0;
	}
	State(int arg_node,int arg_sum_dist){
		node = arg_node;
		sum_dist = arg_sum_dist;
	}
	bool operator<(const struct State& arg) const{

		return sum_dist > arg.sum_dist; //昇順（PQ)
	}

	int node,sum_dist;
};

int num_T = 0;
int base_T;
string word[SIZE],ID[SIZE];
vector<int> G[SIZE];
int min_dist[SIZE];
bool visited[SIZE];
vector<int> V,D;
vector<string> tmp_str[SIZE];
map<string,int> MAP;
int IND;

int N,E;




void dfs(int node,int pre){

	V.push_back(node);

	for(int i = 0; i < G[node].size(); i++){

		int child = G[node][i];
		if(child == pre || visited[child])continue;

		visited[child] = true;
		dfs(child,node);
	}
}

int calc_dist(int start){

	for(int i = 0; i < V.size(); i++){

		min_dist[V[i]] = BIG_NUM;
	}

	min_dist[start] = 0;
	priority_queue<State> Q;

	Q.push(State(start,0));

	while(!Q.empty()){

		State state = Q.top();
		Q.pop();

		if(state.sum_dist > min_dist[state.node])continue;

		for(int i = 0; i < G[state.node].size(); i++){

			int next_node = G[state.node][i];
			int next_dist = state.sum_dist+1;

			if(min_dist[next_node] > next_dist){

				min_dist[next_node] = next_dist;
				Q.push(State(next_node,next_dist));
			}
		}
	}

	int maxi = -1;
	int ret = -1;

	for(int i = 0; i < V.size(); i++){
		if(maxi < min_dist[V[i]]){
			maxi = min_dist[V[i]];
			ret = V[i];
		}
	}

	return ret;
}

bool rec(int node,int pre,int right){

	if(node == right){

		D.push_back(node);
		return true;
	}

	for(int i = 0; i < G[node].size(); i++){

		int child = G[node][i];
		if(child == pre)continue;

		if(rec(child,node,right)){

			D.push_back(node);
			return true;
		}
	}
	return false;
}

//直径の両端を求める
pair<int,int> calc_diameter(int base){

	int a = calc_dist(base);
	int b = calc_dist(a);

	return make_pair(a,b);
}

int getNUM(string str){

	auto at = MAP.find(str);
	if(at == MAP.end()){

		MAP[str] = IND++;
	}
	return MAP[str];
}

string make_word(int node,int pre){

	int num_child = 0;
	for(int i = 0; i < G[node].size(); i++){
		int child = G[node][i];
		if(child == pre)continue;

		num_child++;
	}
	if(num_child == 0){

		word[node] = "0";
		ID[node] = to_string(getNUM(word[node]));

		return ID[node];
	}

	vector<string> vec;
	for(int i = 0; i < G[node].size(); i++){
		int child = G[node][i];
		if(child == pre)continue;

		vec.push_back(make_word(child,node));
	}
	sort(vec.begin(),vec.end());

	string new_str = "";
	for(int i = 0; i < vec.size(); i++){

		new_str += vec[i];
	}

	ID[node] = to_string(getNUM(new_str));
	return ID[node];
}

int main(){

	scanf("%d %d",&N,&E);

	int a,b;

	for(int i = 0; i < E; i++){

		scanf("%d %d",&a,&b);
		a--;
		b--;

		G[a].push_back(b);
		G[b].push_back(a);
	}

	//採番は全体を通じて共通に行う
	IND = 1;

	for(int i = 0; i < N; i++){
		if(visited[i])continue;

		V.clear();
		visited[i] = true;

		dfs(i,-1);
		pair<int,int> lr = calc_diameter(i);
		D.clear();

		//直径上のノードを求める
		rec(lr.first,-1,lr.second);

		int num_P = D.size();
		if(num_P%2 == 1){ //頂点数が奇数

			string tmp = make_word(D[num_P/2],-1);
			tmp_str[num_T].push_back(tmp);

		}else{ //頂点数が偶数

			string tmp = make_word(D[num_P/2],-1);
			tmp_str[num_T].push_back(tmp);

			tmp = make_word(D[num_P/2-1],-1);
			tmp_str[num_T].push_back(tmp);
		}
		num_T++;
	}

	for(int i = 0; i < SIZE; i++){

		G[i].clear();
	}

	scanf("%d",&N);

	for(int i = 0; i < N-1; i++){

		scanf("%d %d",&a,&b);
		a--;
		b--;

		G[a].push_back(b);
		G[b].push_back(a);
	}

	V.clear();
	for(int i = 0; i < N; i++){

		visited[i] = false;
	}

	visited[0] = true;
	dfs(0,-1);

	pair<int,int> lr = calc_diameter(0);
	D.clear();

	//直径上のノードを求める
	rec(lr.first,-1,lr.second);

	int num_P = D.size();
	if(num_P%2 == 1){ //頂点数が奇数

		string tmp = make_word(D[num_P/2],-1);
		tmp_str[num_T].push_back(tmp);

	}else{ //頂点数が偶数

		string tmp = make_word(D[num_P/2],-1);
		tmp_str[num_T].push_back(tmp);

		tmp = make_word(D[num_P/2-1],-1);
		tmp_str[num_T].push_back(tmp);
	}

	base_T = num_T;

	int ans = 0;

	for(int i = 0; i < base_T; i++){
		bool FLG = false;
		for(int a = 0; a < tmp_str[base_T].size(); a++){
			for(int b = 0; b < tmp_str[i].size(); b++){
				if(tmp_str[base_T][a] == tmp_str[i][b]){

					FLG = true;
					break;
				}
			}
			if(FLG)break;
		}
		if(FLG){

			ans++;
		}
	}

	printf("%d\n",ans);

	return 0;
}
accuracy: 1
time_ms: 500
memory_kb: 91136
final_rank: 15

code_snippet: #include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef unsigned long long int ull;
#define BIG_NUM 2000000000
#define HUGE_NUM 4000000000000000000 //オーバーフローに注意
#define MOD 1000000007
#define EPS 0.000000001




#define SIZE 300005

struct State{
	State(){
		node = sum_dist = 0;
	}
	State(int arg_node,int arg_sum_dist){
		node = arg_node;
		sum_dist = arg_sum_dist;
	}
	bool operator<(const struct State& arg) const{

		return sum_dist > arg.sum_dist; //昇順（PQ)
	}

	int node,sum_dist;
};

int num_T = 0;
int base_T;
string word[SIZE],ID[SIZE];
vector<int> G[SIZE];
int min_dist[SIZE];
bool visited[SIZE];
vector<int> V,D;
vector<string> tmp_str[SIZE];
map<string,int> MAP;
int IND;

int N,E;




void dfs(int node,int pre){

	V.push_back(node);

	for(int i = 0; i < G[node].size(); i++){

		int child = G[node][i];
		if(child == pre || visited[child])continue;

		visited[child] = true;
		dfs(child,node);
	}
}

int calc_dist(int start){

	for(int i = 0; i < V.size(); i++){

		min_dist[V[i]] = BIG_NUM;
	}

	min_dist[start] = 0;
	priority_queue<State> Q;

	Q.push(State(start,0));

	while(!Q.empty()){

		State state = Q.top();
		Q.pop();

		if(state.sum_dist > min_dist[state.node])continue;

		for(int i = 0; i < G[state.node].size(); i++){

			int next_node = G[state.node][i];
			int next_dist = state.sum_dist+1;

			if(min_dist[next_node] > next_dist){

				min_dist[next_node] = next_dist;
				Q.push(State(next_node,next_dist));
			}
		}
	}

	int maxi = -1;
	int ret = -1;

	for(int i = 0; i < V.size(); i++){
		if(maxi < min_dist[V[i]]){
			maxi = min_dist[V[i]];
			ret = V[i];
		}
	}

	return ret;
}

bool rec(int node,int pre,int right){

	if(node == right){

		D.push_back(node);
		return true;
	}

	for(int i = 0; i < G[node].size(); i++){

		int child = G[node][i];
		if(child == pre)continue;

		if(rec(child,node,right)){

			D.push_back(node);
			return true;
		}
	}
	return false;
}

//直径の両端を求める
pair<int,int> calc_diameter(int base){

	int a = calc_dist(base);
	int b = calc_dist(a);

	return make_pair(a,b);
}

int getNUM(string str){

	auto at = MAP.find(str);
	if(at == MAP.end()){

		MAP[str] = IND++;
	}
	return MAP[str];
}

string make_word(int node,int pre){

	int num_child = 0;
	for(int i = 0; i < G[node].size(); i++){
		int child = G[node][i];
		if(child == pre)continue;

		num_child++;
	}
	if(num_child == 0){

		word[node] = "0";
		ID[node] = to_string(getNUM(word[node]));

		return ID[node];
	}

	vector<string> vec;
	for(int i = 0; i < G[node].size(); i++){
		int child = G[node][i];
		if(child == pre)continue;

		vec.push_back(make_word(child,node));
	}
	sort(vec.begin(),vec.end());

	string new_str = "";
	for(int i = 0; i < vec.size(); i++){

		new_str += vec[i];
	}

	ID[node] = to_string(getNUM(new_str));
	return ID[node];
}

int main(){

	scanf("%d %d",&N,&E);

	int a,b;

	for(int i = 0; i < E; i++){

		scanf("%d %d",&a,&b);
		a--;
		b--;

		G[a].push_back(b);
		G[b].push_back(a);
	}

	IND = 1;

	for(int i = 0; i < N; i++){
		if(visited[i])continue;

		V.clear();
		visited[i] = true;

		dfs(i,-1);
		pair<int,int> lr = calc_diameter(i);
		D.clear();

		//直径上のノードを求める
		rec(lr.first,-1,lr.second);

		int num_P = D.size();
		if(num_P%2 == 1){ //頂点数が奇数

			string tmp = make_word(D[num_P/2],-1);
			//printf("森[%d]:%s\n",i,tmp.c_str());
			tmp_str[num_T].push_back(tmp);

		}else{ //頂点数が偶数

			string tmp = make_word(D[num_P/2],-1);
			tmp_str[num_T].push_back(tmp);
			//printf("森[%d]:%s\n",i,tmp.c_str());


			tmp = make_word(D[num_P/2-1],-1);
			tmp_str[num_T].push_back(tmp);
			//printf("森[%d]:%s\n",i,tmp.c_str());
		}
		num_T++;
	}

	for(int i = 0; i < SIZE; i++){

		G[i].clear();
	}

	scanf("%d",&N);

	for(int i = 0; i < N-1; i++){

		scanf("%d %d",&a,&b);
		a--;
		b--;

		G[a].push_back(b);
		G[b].push_back(a);
	}

	V.clear();
	for(int i = 0; i < N; i++){

		visited[i] = false;
	}

	visited[0] = true;
	dfs(0,-1);

	pair<int,int> lr = calc_diameter(0);
	//printf("base 直径の両端:%d-%d\n",lr.first,lr.second);
	D.clear();

	//直径上のノードを求める
	rec(lr.first,-1,lr.second);

	int num_P = D.size();
	if(num_P%2 == 1){ //頂点数が奇数

		string tmp = make_word(D[num_P/2],-1);
		tmp_str[num_T].push_back(tmp);
		//printf("base:%s\n",tmp.c_str());

	}else{ //頂点数が偶数

		string tmp = make_word(D[num_P/2],-1);
		tmp_str[num_T].push_back(tmp);
		//printf("base:%s\n",tmp.c_str());

		tmp = make_word(D[num_P/2-1],-1);
		tmp_str[num_T].push_back(tmp);
		//printf("base:%s\n",tmp.c_str());
	}
	base_T = num_T;

	int ans = 0;

	for(int i = 0; i < base_T; i++){
		bool FLG = false;
		for(int a = 0; a < tmp_str[base_T].size(); a++){
			for(int b = 0; b < tmp_str[i].size(); b++){
				if(tmp_str[base_T][a] == tmp_str[i][b]){

					FLG = true;
					break;
				}
			}
			if(FLG)break;
		}
		if(FLG){

			ans++;
		}
	}

	printf("%d\n",ans);

	return 0;
}
accuracy: 1
time_ms: 520
memory_kb: 91220
final_rank: 16

code_snippet: #include<bits/stdc++.h>
using namespace std;
#define ALL(x) begin(x),end(x)
#define rep(i,n) for(int i=0;i<(n);i++)
#define debug(v) cout<<#v<<":";for(auto x:v){cout<<x<<' ';}cout<<endl;
#define mod 1000000007
using ll=long long;
const int INF=1000000000;
const ll LINF=1001002003004005006ll;
int dx[]={1,0,-1,0},dy[]={0,1,0,-1};
template<class T>bool chmax(T &a,const T &b){if(a<b){a=b;return true;}return false;}
template<class T>bool chmin(T &a,const T &b){if(b<a){a=b;return true;}return false;}

#define PROBLEM "https://onlinejudge.u-aizu.ac.jp/problems/2821"

// hash type
using ull=unsigned long long;
struct TreeHasher{
    using uint128=__uint128_t;
    static const ull Mod=(1ull<<61ull)-1;
    static inline ull add(ull a,ull b){if((a+=b)>=Mod)a-=Mod;return a;}
    static inline ull mul(ull a,ull b){uint128 c=(uint128)a*b;return add(c>>61,c&Mod);}
    vector<ull> R;


    // 木のサイズ
    TreeHasher(int n){
        R.push_back(1);
        mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count());
        uniform_int_distribution<ull> rand(1,Mod-1);
        for(int i=1;i<=n;i++) R.push_back(rand(mt));
        // R.resize(n+1);
        // iota(ALL(R),1);
    }
    inline vector<int> BFS(vector<vector<int>> &g,int st,vector<int> &dis,bool memo_parent=false){
        vector<int> par;
        if(memo_parent) par.assign(g.size(),-1);
        dis.assign(g.size(),-1);
        dis[st]=0;
        queue<int> que;
        que.push(st);
        while(!que.empty()){
            int cur=que.front();que.pop();
            for(auto &to:g[cur]){
                if(dis[to]>=0) continue;
                dis[to]=dis[cur]+1;
                if(memo_parent) par[to]=cur;
                que.push(to);
            }
        }
        return par;
    }

    // 木の中心(直径の真ん中)を返す
    vector<int> center(vector<vector<int>> &g){
        vector<int> d;
        BFS(g,0,d);
        int u=max_element(begin(d),end(d))-begin(d);
        
        auto par=BFS(g,u,d,true);
        int v=max_element(begin(d),end(d))-begin(d);
        int diameter=d[v];
        for(int i=0;i<diameter/2;i++) v=par[v];
        vector<int> ret;
        ret.push_back(v);
        if(diameter%2) ret.push_back(par[v]);
        return ret;
    }

    ull RootedTreeHash(vector<vector<int>> &g,int root){
        vector<int> dis(g.size(),-1);
        vector<ull> h(g.size());
        function<void(int,int)> dfs=[&](int par,int cur){
            int d=-1;
            for(auto &to:g[cur])if(par!=to){
                dfs(cur,to);
                chmax(d,dis[to]);
            }
            d++;
            dis[cur]=d;
            ull ret=1;
            // rng hash
            for(auto &to:g[cur])if(par!=to) ret=mul(ret,add(h[to],R[d]));
            h[cur]=ret;
        };
        dfs(-1,root);
        return h[root];
    }
    // 木の中心からhashをとる．木の中心は2個ある可能性がある
    ull TreeHash(vector<vector<int>> &g){
        auto centers=center(g);
        ull ret=RootedTreeHash(g,centers[0]);
        if(centers.size()==2) ret=min(ret,RootedTreeHash(g,centers[1]));
        return ret;
    }
    bool RootedTreeIsomorphic(vector<vector<int>> &g,int g_root,vector<vector<int>> &h,int h_root){
        if(g.size()!=h.size()) return false;
        return (RootedTreeHash(g,g_root)==RootedTreeHash(h,h_root));
    }
    bool TreeIsomorphic(vector<vector<int>> &g,vector<vector<int>> &h){
        if(g.size()!=h.size()) return false;
        return (TreeHash(g)==TreeHash(h));
        }
};

struct UnionFind{
    int con;
    vector<int> par,siz;
    UnionFind(int n):con(n),par(n),siz(n,1){
        iota(begin(par),end(par),0);
    }
    int root(int x){
        return (par[x]==x?x:(par[x]=root(par[x])));
    }
    bool sameroot(int x,int y){
        return root(x)==root(y);
    }
    bool unite(int x,int y){
        x=root(x);y=root(y);
        if(x==y) return false;
        if(siz[x]<siz[y])swap(x,y);
        siz[x]+=siz[y];
        par[y]=x;
        con--;
        return true;
    }
    int size(int x){
        return siz[root(x)];
    }
};

signed main(){
    TreeHasher th(300010);

    int n,m;cin>>n>>m;
    vector<vector<int>> g(n);
    UnionFind uf(n);
    rep(i,m){
        int u,v;cin>>u>>v;u--,v--;
        uf.unite(u,v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    map<int,vector<int>> vertices;
    rep(i,n) vertices[uf.root(i)].push_back(i);

    // hashごとカウント
    map<ull,int> cnt;
    vector<int> id(n);
    for(auto &p:vertices){
        auto &v=p.second;
        rep(i,(int)v.size()) id[v[i]]=i;
        vector<vector<int>> t(v.size());
        rep(i,(int)v.size()){
            for(auto &j:g[v[i]]) t[i].push_back(id[j]);
        }
        cnt[th.TreeHash(t)]++;
    }
    
    cin>>n;
    vector<vector<int>> t(n);
    rep(i,n-1){
        int u,v;cin>>u>>v;u--,v--;
        t[u].push_back(v);
        t[v].push_back(u);
    }
    cout<<cnt[th.TreeHash(t)]<<endl;
    return 0;
}
accuracy: 1
time_ms: 320
memory_kb: 63292
final_rank: 7

code_snippet: #include <bits/stdc++.h>
#define MOD (long long)(1e9 + 7)
using namespace std;

vector<int> centroids(const vector<vector<int>> &g) {
  int n = g.size();
  vector<int> sz(n, 0), res;
  auto dfs = [&](int now, int par, auto &&dfs) -> void {
    sz[now] = 1;
    bool ch = true;
    for (auto to : g[now])
      if (to != par) {
        dfs(to, now, dfs);
        if (sz[to] > n / 2) ch = false;
        sz[now] += sz[to];
      }
    if (n - sz[now] > n / 2) ch = false;
    if (ch) res.push_back(now);
  };
  dfs(0, n, dfs);
  return res;
}

unsigned int xorshift() {
  static unsigned int nowx = 123456789, nowy = 362436069, nowz = 521288629,
                      noww = 88675123;
  unsigned int temp;
  temp = (nowx ^ (nowx << 11));
  nowx = nowy;
  nowy = nowz;
  nowz = noww;
  return (noww = (noww ^ (noww >> 19)) ^ (temp ^ (temp >> 8)));
}

int n, m, n2;
vector<vector<int>> g, g2;
vector<long long> hashs;

int solve();
long long calc_hash(int now, int par, const vector<vector<int>> &g,
                    int dist = 0);

int main() {
  cin >> n >> m;
  for (int i = 0; i < n; ++i) hashs.push_back(xorshift());
  g.resize(n);
  for (int i = 0; i < m; ++i) {
    int u, v;
    cin >> u >> v;
    g[--u].push_back(--v);
    g[v].push_back(u);
  }
  cin >> n2;
  g2.resize(n2);
  for (int i = 1; i < n2; ++i) {
    int u, v;
    cin >> u >> v;
    g2[--u].push_back(--v);
    g2[v].push_back(u);
  }
  cout << solve() << endl;
  return 0;
}

int solve() {
  int res = 0, targethash = MOD;
  auto roots = centroids(g2);
  for (auto p : roots) targethash = min(targethash, (int)calc_hash(p, -1, g2));
  vector<bool> used(n, 0);
  for (int i = 0; i < n; ++i)
    if (!used[i]) {
      vector<vector<int>> ng(1);
      queue<int> qu;
      map<int, int> mp;
      mp[i] = 0;
      qu.push(i);
      while (qu.size()) {
        int now = qu.front();
        qu.pop();
        used[now] = 1;
        for (auto to : g[now])
          if (!mp.count(to)) {
            mp[to] = mp.size();
            ng.push_back(vector<int>());
            int x = mp[now], y = mp[to];
            ng[x].push_back(y);
            ng[y].push_back(x);
            qu.push(to);
          }
      }
      if (n2 != ng.size()) continue;
      auto r = centroids(ng);
      long long now = MOD;
      for (auto p : r) now = min(now, calc_hash(p, -1, ng));
      if (now == targethash) ++res;
    }
  return res;
}

long long calc_hash(int now, int par, const vector<vector<int>> &g, int dist) {
  long long res = 0;
  bool ch = 1;
  for (auto to : g[now])
    if (to != par) {
      long long p = calc_hash(to, now, g);
      res += p * p;
      res %= MOD;
      ch = 0;
    }
  if (ch)
    res = 1;
  else
    res += hashs[dist];
  return res % MOD;
}
accuracy: 1
time_ms: 440
memory_kb: 60632
final_rank: 10

code_snippet: #include <bits/stdc++.h>

using namespace std;

#define REP(i, n) for (int i=0; i<(n); ++i)
#define RREP(i, n) for (int i=(int)(n)-1; i>=0; --i)
#define FOR(i, a, n) for (int i=(a); i<(n); ++i)
#define RFOR(i, a, n) for (int i=(int)(n)-1; i>=(a); --i)

#define SZ(x) ((int)(x).size())
#define ALL(x) (x).begin(),(x).end()

#define DUMP(x) cerr<<#x<<" = "<<(x)<<endl
#define DEBUG(x) cerr<<#x<<" = "<<(x)<<" (L"<<__LINE__<<")"<<endl;

template<class T>
ostream &operator<<(ostream &os, const vector <T> &v) {
    os << "[";
    REP(i, SZ(v)) {
        if (i) os << ", ";
        os << v[i];
    }
    return os << "]";
}

template<class T, class U>
ostream &operator<<(ostream &os, const pair <T, U> &p) {
    return os << "(" << p.first << " " << p.second << ")";
}

template<class T>
bool chmax(T &a, const T &b) {
    if (a < b) {
        a = b;
        return true;
    }
    return false;
}

template<class T>
bool chmin(T &a, const T &b) {
    if (b < a) {
        a = b;
        return true;
    }
    return false;
}

using ll = long long;
using ull = unsigned long long;
using ld = long double;
using P = pair<int, int>;
using vi = vector<int>;
using vll = vector<ll>;
using vvi = vector<vi>;
using vvll = vector<vll>;

const ll MOD = 1e9 + 7;
const int INF = INT_MAX / 2;
const ll LINF = LLONG_MAX / 2;
const ld eps = 1e-9;


template<int m>
struct mint {
    int x;

    mint(int x = 0) : x(((x % m) + m) % m) {}

    mint operator-() { return x ? m - x : 0; }

    mint &operator+=(mint r) {
        if ((x += r.x) >= m) x -= m;
        return *this;
    }

    mint &operator-=(mint r) {
        if ((x -= r.x) < 0) x += m;
        return *this;
    }

    mint &operator*=(mint r) {
        x = ((ll) x * r.x) % m;
        return *this;
    }

    mint inv() {
        int a = x, b = m, u = 1, v = 0, t;
        while (b > 0) {
            t = a / b;
            swap(a -= t * b, b);
            swap(u -= t * v, v);
        }
        return u;
    }

    mint &operator/=(mint r) { return *this *= r.inv(); }

    friend mint operator+(mint l, mint r) { return l += r; }

    friend mint operator-(mint l, mint r) { return l -= r; }

    friend mint operator*(mint l, mint r) { return l *= r; }

    friend mint operator/(mint l, mint r) { return l /= r; }

    mint pow(ll n) {
        mint ret = 1, tmp = *this;
        while (n) {
            if (n & 1) ret *= tmp;
            tmp *= tmp, n >>= 1;
        }
        return ret;
    }

    friend bool operator==(mint l, mint r) { return l.x == r.x; }

    friend bool operator!=(mint l, mint r) { return l.x != r.x; }

    friend ostream &operator<<(ostream &os, mint a) {
        return os << a.x;
    }

    friend istream &operator>>(istream &is, mint &a) {
        int x;
        is >> x;
        a = x;
        return is;
    }
};


template<typename T>
struct Combination {
    int _n = 1;
    vector<T> _fact{1}, _rfact{1};

    void extend(int n) {
        if (n <= _n) return;
        _fact.resize(n);
        _rfact.resize(n);
        for (int i = _n; i < n; ++i) _fact[i] = _fact[i - 1] * i;
        _rfact[n - 1] = 1 / _fact[n - 1];
        for (int i = n - 1; i > _n; --i) _rfact[i - 1] = _rfact[i] * i;
        _n = n;
    }

    T fact(int k) {
        extend(k + 1);
        return _fact.at(k);
    }

    T rfact(int k) {
        extend(k + 1);
        return _rfact.at(k);
    }

    T P(int n, int r) {
        if (r < 0 or n < r) return 0;
        return fact(n) * rfact(n - r);
    }

    T C(int n, int r) {
        if (r < 0 or n < r) return 0;
        return fact(n) * rfact(r) * rfact(n - r);
    }

    T H(int n, int r) {
        return (n == 0 and r == 0) ? 1 : C(n + r - 1, r);
    }
};


/**
 * @brief 文字列のハッシュ化
 * @author habara-k
 * @date 2020/10/28
 * @usage
 *   auto base = RollingHash::gen_base();
 *   string s; cin >> s;
 *   RollingHash hash(s, base);   // O(|s|)
 *   hash.get(l, r);   // s[l,r)のハッシュ. O(1)
 */
struct RollingHash {
    using u64 = uint64_t;
    using i128 = __int128_t;

    template<typename S>
    RollingHash(const S &s, u64 base) {
        int n = s.size();
        hash.assign(n+1, 0);
        pow.assign(n+1, 1);
        for (int i = 0; i < n; ++i) {
            hash[i+1] = mul(hash[i], base) + s[i];
            if (hash[i+1] >= MOD) hash[i+1] -= MOD;
            pow[i+1] = mul(pow[i], base);
        }
    }

    u64 get(int l, int r) const {
        u64 ret = hash[r] + MOD - mul(hash[l], pow[r-l]);
        return ret >= MOD ? ret - MOD : ret;
    }

    static u64 gen_base() {
        mt19937 random{random_device{}()};
        uniform_int_distribution<u64> dist(2, MOD-2);
        return dist(random);
    }

private:
    vector<u64> hash, pow;
    static const u64 MOD = (1ul << 61) - 1;

    static u64 mul(i128 a, i128 b) {
        i128 t = a * b;
        t = (t >> 61) + (t & MOD);
        if (t >= MOD) t -= MOD;
        return t;
    }
};

/**
 * @brief 2次元文字列のハッシュ化
 * @author habara-k
 * @date 2020/10/28
 * @usage
 *   auto base1 = RollingHash2D::gen_base(),
 *        base2 = RolligHash2D::gen_base();
 *   vector<string> vs;
 *   RollingHash2D hash(vs, base1, base2);
 *   hash.get(i1, j1, i2, j2);
 */
struct RollingHash2D {
    using u64 = uint64_t;
    using i128 = __int128_t;

    template<typename VS>
    RollingHash2D(const VS &vs, u64 base1, u64 base2) {
        int n = vs.size();
        int m = vs[0].size();
        hash.assign(n+1, vector<u64>(m+1));

        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                hash[i+1][j+1] = mul(hash[i+1][j], base2) + vs[i][j];
                if (hash[i+1][j+1] >= MOD) hash[i+1][j+1] -= MOD;
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                hash[i+1][j+1] = mul(hash[i][j+1], base1) + hash[i+1][j+1];
                if (hash[i+1][j+1] >= MOD) hash[i+1][j+1] -= MOD;
            }
        }

        pow1.assign(n+1, 1);
        for (int i = 0; i < n; ++i) {
            pow1[i + 1] = mul(pow1[i], base1);
        }
        pow2.assign(m+1, 1);
        for (int j = 0; j < m; ++j) {
            pow2[j + 1] = mul(pow2[j], base2);
        }
    }

    long long get(int i0, int j0, int i1, int j1) {
        u64 a = hash[i1][j1] + MOD - mul(hash[i1][j0], pow2[j1-j0]);
        if (a >= MOD) a -= MOD;
        u64 b = hash[i0][j1] + MOD - mul(hash[i0][j0], pow2[j1-j0]);
        if (b >= MOD) b -= MOD;
        u64 ret = a + MOD - mul(b, pow1[i1-i0]);
        return ret >= MOD ? ret - MOD : ret;
    }

    static u64 gen_base() {
        mt19937 random{random_device{}()};
        uniform_int_distribution <u64> dist(2, MOD - 2);
        return dist(random);
    }

private:
    vector<vector<u64>> hash;
    vector<u64> pow1, pow2;
    static const u64 MOD = (1ul << 61) - 1;

    static u64 mul(i128 a, i128 b) {
        i128 t = a * b;
        t = (t >> 61) + (t & MOD);
        if (t >= MOD) t -= MOD;
        return t;
    }
};

/**
 * @brief 木の重心列挙. O(n)
 * @author habara-k
 * @date 2020/10/14
 */
vector<int> centroid(const vector<vector<int>>& g) {
    const int n = g.size();
    vector<int> sz(n,1), ret;
    function<int(int,int)> dfs = [&](int u,int p) {
        bool isCent = true;
        for (int v : g[u]) {
            if (v == p) continue;
            sz[u] += dfs(v, u);
            if (sz[v] > n / 2) isCent = false;
        }
        if (n - sz[u] > n / 2) isCent = false;
        if (isCent) ret.emplace_back(u);
        return sz[u];
    };
    dfs(0, -1);
    return ret;
}

struct UnionFind
{
    vector<int> par, sz;
    UnionFind(int n) : par(n), sz(n, 1) {
        for (int i = 0; i < n; ++i) par[i] = i;
    }
    int root(int x) {
        if (par[x] == x) return x;
        return par[x] = root(par[x]);
    }
    void merge(int x, int y) {
        x = root(x);
        y = root(y);
        if (x == y) return;
        if (sz[x] < sz[y]) swap(x, y);
        par[y] = x;
        sz[x] += sz[y];
        sz[y] = 0;
    }
    bool issame(int x, int y) {
        return root(x) == root(y);
    }
    int size(int x) {
        return sz[root(x)];
    }
};


/**
 * @brief 木のハッシュ化
 * @author habara-k
 * @date 2020/10/28
 * @usage
 *   vector<vector<int>> g;
 *   int seed = 0;
 *   auto hash = RootedTreeHash(g, seed).get();
 */
struct TreeHash {
    using u64 = uint64_t;
    using i128 = __int128_t;

    TreeHash(const vector<vector<int>>& g, int seed=0) : g(g) {
        int n = g.size();
        mt19937 random(seed);
        uniform_int_distribution<u64> dist(2, MOD-2);
        for (int i = 0; i < n; ++i) base.emplace_back(dist(random));
    }

    u64 get() {
        vector<u64> hash;
        for (int root : centroid(g)) {
            hash.emplace_back(dfs(root, -1, 0));
        }
        return *min_element(hash.begin(), hash.end());
    }

private:
    vector<u64> base;
    vector<vector<int>> g;
    static const u64 MOD = (1ul << 61) - 1;

    static u64 mul(i128 a, i128 b) {
        i128 t = a * b;
        t = (t >> 61) + (t & MOD);
        if (t >= MOD) t -= MOD;
        return t;
    }

    u64 dfs(int u, int p, int d) {
        u64 hash = 1;
        for (int v : g[u]) {
            if (v == p) continue;
            hash = mul(hash, dfs(v, u, d+1));
        }
        hash += base[d];
        return hash >= MOD ? hash - MOD : hash;
    }
};

int main() {
    int n1, m1; cin >> n1 >> m1;
    vector<vector<int>> g1(n1);
    UnionFind uf(n1);
    REP(i, m1) {
        int u, v; cin >> u >> v; --u, --v;
        g1[u].emplace_back(v);
        g1[v].emplace_back(u);
        uf.merge(u, v);
    }
    int n2; cin >> n2;
    vector<vector<int>> g2(n2);
    REP(i, n2-1) {
        int u, v; cin >> u >> v; --u, --v;
        g2[u].emplace_back(v);
        g2[v].emplace_back(u);
    }

    vector<int> roots;
    for (int i = 0; i < n1; ++i) if (uf.root(i) == i) roots.emplace_back(i);

    map<int,map<int,int>> ord;
    for (int i = 0; i < n1; ++i) {
        ord[uf.root(i)][i] = -1;
    }

    map<int,vector<vector<int>>> g;
    for (auto& tp : ord) {
        int cnt = 0;
        for (auto& p : tp.second) {
            p.second = cnt++;
        }
        g[tp.first].resize(cnt);
    }

    for (int u = 0; u < n1; ++u) {
        int r = uf.root(u);
        for (int v : g1[u]) {
            g[r][ord[r][u]].emplace_back(ord[r][v]);
        }
    }

    int ans = 0;
    auto hash = TreeHash(g2).get();
    for (auto &tp : g) {
        auto h = TreeHash(tp.second).get();
        ans += h == hash;
    }

    cout << ans << endl;
}
accuracy: 1
time_ms: 860
memory_kb: 87132
final_rank: 17