Problem:
Twin Trees Bros.
To meet the demand of ICPC (International Cacao Plantation Consortium), you have to check whether two given
trees
are
twins
or not.
Example of two trees in the three-dimensional space.
The term
tree
in the graph theory means a connected graph where the number of edges is one less than the number of nodes. ICPC, in addition, gives three-dimensional grid points as the locations of the tree nodes. Their definition of two trees being
twins
is that, there exists a geometric transformation function which gives a one-to-one mapping of all the nodes of one tree to the nodes of the other such that for each edge of one tree, there exists an edge in the other tree connecting the corresponding nodes. The geometric transformation should be a combination of the following transformations:
translations, in which coordinate values are added with some constants,
uniform scaling with positive scale factors, in which all three coordinate values are multiplied by the same positive constant, and
rotations of any amounts around either $x$-, $y$-, and $z$-axes.
Note that two trees can be twins in more than one way, that is, with different correspondences of nodes.
Write a program that decides whether two trees are twins or not and outputs the number of different node correspondences.
Hereinafter, transformations will be described in the right-handed $xyz$-coordinate system.
Trees in the sample inputs 1 through 4 are shown in the following figures. The numbers in the figures are the node numbers defined below.
For the sample input 1, each node of the red tree is mapped to the corresponding node of the blue tree by the transformation that translates $(-3, 0, 0)$, rotates $-\pi / 2$ around the $z$-axis, rotates $\pi / 4$ around the $x$-axis, and finally scales by $\sqrt{2}$. By this mapping, nodes #1, #2, and #3 of the red tree at $(0, 0, 0)$, $(1, 0, 0)$, and $(3, 0, 0)$ correspond to nodes #6, #5, and #4 of the blue tree at $(0, 3, 3)$, $(0, 2, 2)$, and $(0, 0, 0)$, respectively. This is the only possible correspondence of the twin trees.
For the sample input 2, red nodes #1, #2, #3, and #4 can be mapped to blue nodes #6, #5, #7, and #8. Another node correspondence exists that maps nodes #1, #2, #3, and #4 to #6, #5, #8, and #7.
For the sample input 3, the two trees are not twins. There exist transformations that map nodes of one tree to distinct nodes of the other, but the edge connections do not agree.
For the sample input 4, there is no transformation that maps nodes of one tree to those of the other.
Input
The input consists of a single test case of the following format.
$n$
$x_1$ $y_1$ $z_1$
.
.
.
$x_n$ $y_n$ $z_n$
$u_1$ $v_1$
.
.
.
$u_{n−1}$ $v_{n−1}$
$x_{n+1}$ $y_{n+1}$ $z_{n+1}$
.
.
.
$x_{2n}$ $y_{2n}$ $z_{2n}$
$u_n$ $v_n$
.
.
.
$u_{2n−2}$ $v_{2n−2}$
The input describes two trees. The first line contains an integer $n$ representing the number of nodes of each tree ($3 \leq n \leq 200$). Descriptions of two trees follow.
Description of a tree cons
...(truncated)

Submitted solutions:

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

using ll = long long;

template<class T> struct Point3D {
	typedef Point3D P;
	typedef const P& R;
	T x, y, z;
	explicit Point3D(T x=0, T y=0, T z=0) : x(x), y(y), z(z) {}
	bool operator<(R p) const {
		return tie(x, y, z) < tie(p.x, p.y, p.z); }
	bool operator==(R p) const {
		return tie(x, y, z) == tie(p.x, p.y, p.z); }
	P operator+(R p) const { return P(x+p.x, y+p.y, z+p.z); }
	P operator-(R p) const { return P(x-p.x, y-p.y, z-p.z); }
	P operator*(T d) const { return P(x*d, y*d, z*d); }
	P operator/(T d) const { return P(x/d, y/d, z/d); }
	T dot(R p) const { return x*p.x + y*p.y + z*p.z; }
	P cross(R p) const {
		return P(y*p.z - z*p.y, z*p.x - x*p.z, x*p.y - y*p.x);
	}
	T dist2() const { return x*x + y*y + z*z; }
	double dist() const { return sqrt((double)dist2()); }
	//Azimuthal angle (longitude) to x-axis in interval [-pi, pi]
	double phi() const { return atan2(y, x); } 
	//Zenith angle (latitude) to the z-axis in interval [0, pi]
	double theta() const { return atan2(sqrt(x*x+y*y),z); }
	P unit() const { return *this/(T)dist(); } //makes dist()=1
	//returns unit vector normal to *this and p
	P normal(P p) const { return cross(p).unit(); }
	//returns point rotated 'angle' radians ccw around axis
	P rotate(double angle, P axis) const {
		double s = sin(angle), c = cos(angle); P u = axis.unit();
		return u*dot(u)*(1-c) + (*this)*c - cross(u)*s;
	}
};

using P = Point3D<ll>;

int main(){
	ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	vector<vector<P> > p(2, vector<P>(n));
	vector<vector<vector<int> > > edges(2, vector<vector<int> > (n));
	for(int z = 0; z < 2; z++){
		for(int j = 0; j < n; j++){
			cin >> p[z][j].x >> p[z][j].y >> p[z][j].z;
		}
		for(int j = 0; j < n-1; j++){
			int a, b;
			cin >> a >> b;
			a--; b--;
			a %= n; b %= n;
			edges[z][a].push_back(b);
			edges[z][b].push_back(a);
		}
	}
	int a = -1, b = -1, c = -1;
	for(int i = 0; i < n; i++){
		for(int j : edges[0][i]){
			for(int k : edges[0][i]){
				// n^2 here
				if(j == k) continue;
				if(a == -1 || !((p[0][j] - p[0][i]).cross(p[0][k] - p[0][i]) == P(0,0,0)) ){
					a = i;
					b = j;
					c = k;
				}
			}
		}
	}
	assert(a >= 0);
	int ans = 0;
	for(int i = 0; i < n; i++){
		for(int j : edges[1][i]){
			for(int k : edges[1][i]){
				// n^2 here
				if(j == k) continue;
				vector<ll> gs(2);
				for(int z = 0; z < 2; z++){
					vector<int> ref;
					if(z == 0) ref = {a, b, c};
					if(z == 1) ref = {i, j, k};
					ll g = __gcd((p[z][ref[0]]-p[z][ref[1]]).dist2(), 
						   __gcd((p[z][ref[0]]-p[z][ref[2]]).dist2(), 
							     (p[z][ref[1]]-p[z][ref[2]]).dist2()));
					gs[z] = abs(g);
				}
				vector<pair<vector<ll>, ll> > where[2];
				vector<int> inv[2];
				for(int z = 0; z < 2; z++){
					inv[z].resize(n);
					vector<int> ref;
					if(z == 0) ref = {a, b, c};
					if(z == 1) ref = {i, j, k};
					ll multg = gs[1 ^ z];
					P cr = (p[z][ref[2]] - p[z][ref[0]]).cross(p[z][ref[1]] - p[z][ref[0]]);
					for(int q = 0; q < n; q++){
						ll r = (p[z][q] - p[z][ref[0]]).dist2() * multg;
						ll s = (p[z][q] - p[z][ref[1]]).dist2() * multg;
						ll t = (p[z][q] - p[z][ref[2]]).dist2() * multg;
						ll u = (p[z][q] - p[z][ref[0]]).dot(cr);
						if(u > 0) u = 1;
						if(u < 0) u = -1;
						where[z].push_back({{r, s, t, u}, q});
					}
					sort(where[z].begin(), where[z].end());
					for(int q = 0; q < n; q++){
						inv[z][where[z][q].second] = q;
					}
				}
				bool bad = false;
				for(int q = 0; q < n; q++){
					if(where[0][q].first != where[1][q].first){
						bad = true;
						break;
					}
				}
				if(bad) continue;
				vector<pair<int,int> > new_edges[2];
				for(int z = 0; z < 2; z++){
					for(int q = 0; q < n; q++){
						for(int r : edges[z][q]){
							new_edges[z].push_back({inv[z][q], inv[z][r]});
						}
					}
					sort(new_edges[z].begin(), new_edges[z].end());
				}
				if(new_edges[0] == new_edges[1]){
					ans++;
				}
			}
		}
	}
	cout << ans << '\n';
}
accuracy: 1
time_ms: 1210
memory_kb: 3556
final_rank: 15

code_snippet: #ifdef LOCAL
#define _GLIBCXX_DEBUG
#endif
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using vi = vector<int>;
#define rep(i, x)    for(int i = 0; i < (x); i++)
#define rng(i, l, r) for(int i = (l); i < (r); i++)
#define all(v)       (v).begin(), (v).end()
#define sz(v)        (int)(v).size()

template <class T>
void view(const T &e) {
	cerr << e;
}
template <class T>
void view(const vector<T> &v) {
	if(v.empty()) {
		cerr << "{ }";
		return;
	}
	cerr << "{ ";
	for(const auto &e : v) {
		view(e);
		cerr << ", ";
	}
	cerr << "\b\b] }";
}


void debug_out() {}
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
	view(H);
	cerr << ", ";
	debug_out(T...);
}

#ifdef LOCAL
#define debug(...)                                          \
	do {                                                    \
		cerr << __LINE__ << "[" << #__VA_ARGS__ << "] : ["; \
		debug_out(__VA_ARGS__);                             \
		cerr << "\b\b]\n";                                  \
	} while(0)
#else
#define debug(...) (void(0))
#endif


struct segtree {
	using T = int;
	static constexpr T unit = INT_MAX;
	T f(T a, T b) { return min(a, b); }
	vector<T> s;
	int n;
	segtree(int n = 0, T def = unit) : s(2 * n, def), n(n) {}
	void update(int pos, T val) {
		for(s[pos += n] = val; pos /= 2;) s[pos] = f(s[pos * 2], s[pos * 2 + 1]);
	}
	T query(int b, int e) {
		T ra = unit, rb = unit;
		for(b += n, e += n; b < e; b /= 2, e /= 2) {
			if(b % 2) ra = f(ra, s[b++]);
			if(e % 2) rb = f(s[--e], rb);
		}
		return f(ra, rb);
	}
};


const int mod = 1000000007;
const int inf = 1 << 30;

struct ft {
	vector<ll> s;
	ft(int n) : s(n) {}
	void add(int p, ll c) {
		for(; p < sz(s); p |= p + 1) {
			s[p] += c;
			s[p] %= mod;
		}
	}
	ll query(int p) {
		ll res = 0;
		for(; p > 0; p &= p - 1) {
			res += s[p - 1];
			res %= mod;
		}
		return res;
	}
	ll query(int l, int r) {
		ll ret = query(r) - query(l);
		ret %= mod;
		return ret;
	}
};

using P = tuple<double, double, double>;

int main() {
	int n;
	cin >> n;
	vector<P> pa(n), pb(n);
	vector<vi> a(n), b(n);
	vector<pii> ea(n - 1), eb(n - 1);
	rep(i, n) {
		int x, y, z;
		cin >> x >> y >> z;
		pa[i] = P{x, y, z};
	}
	rep(i, n - 1) {
		int u, v;
		cin >> u >> v;
		--u, --v;
		ea[i] = {u, v};
		a[u].push_back(v);
		a[v].push_back(u);
	}
	rep(i, n) {
		int x, y, z;
		cin >> x >> y >> z;
		pb[i] = P{x, y, z};
	}
	rep(i, n - 1) {
		int u, v;
		cin >> u >> v;
		--u, --v;
		u -= n, v -= n;
		eb[i] = {u, v};
		b[u].push_back(v);
		b[v].push_back(u);
	}


	auto bfs = [&](vector<vi> &g, int s) {
		vector<int> dist(sz(g), inf);
		dist[s] = 0;
		queue<int> que;
		que.push(s);
		while(sz(que)) {
			auto v = que.front();
			que.pop();
			for(auto to : g[v]) {
				if(dist[to] == inf) {
					dist[to] = dist[v] + 1;
					que.push(to);
				}
			}
		}
		return dist;
	};
	auto med = [&](vector<vi> &g) {
		auto d0 = bfs(g, 0);
		int mx = -1, v = -1;
		rep(i, n) {
			if(mx < d0[i]) {
				mx = d0[i];
				v = i;
			}
		}
		auto dv = bfs(g, v);
		mx = -1;
		int u = -1;
		rep(i, n) {
			if(mx < dv[i]) {
				mx = dv[i];
				u = i;
			}
		}
		auto dist = bfs(g, u);
		debug(dist);
		debug(dv);
		vi ret;
		rep(i, n) {
			if(dist[i] + dv[i] == dist[v]) {
				if(dist[v] % 2 == 0) {
					if(dist[i] == dv[i]) {
						ret.push_back(i);
					}
				} else {
					if(abs(dist[i] - dv[i]) == 1) {
						ret.push_back(i);
					}
				}
			}
		}
		return ret;
	};
	vi amed = med(a);
	vi bmed = med(b);
	if(sz(amed) != sz(bmed)) {
		cout << 0 << endl;
		return 0;
	}


	debug(amed);
	debug(bmed);

	if(sz(amed) == 2) {
		n++;
		a.resize(n);
		b.resize(n);
		{
			auto [x1, y1, z1] = pa[amed[0]];
			auto [x2, y2, z2] = pa[amed[1]];
			pa.push_back(P{(x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2});
			a[amed[0]].push_back(n - 1);
			ea.push_back({amed[0], n - 1});
			a[amed[1]].push_back(n - 1);
			ea.push_back({amed[1], n - 1});
			a[n - 1].push_back(amed[0]);
			a[n - 1].push_back(amed[1]);
			rep(i, sz(ea)) {
				if(ea[i] == pii{amed[0], amed[1]} or ea[i] == pii{amed[1], amed[0]}) {
					ea.erase(ea.begin() + i);
					break;
				}
			}
		}
		{
			auto [x1, y1, z1] = pb[bmed[0]];
			auto [x2, y2, z2] = pb[bmed[1]];
			pb.push_back(P{(x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2});
			b[bmed[0]].push_back(n - 1);
			b[bmed[1]].push_back(n - 1);
			eb.push_back({bmed[0], n - 1});
			eb.push_back({bmed[1], n - 1});
			b[n - 1].push_back(bmed[0]);
			b[n - 1].push_back(bmed[1]);
			rep(i, sz(eb)) {
				if(eb[i] == pii{bmed[0], bmed[1]} or eb[i] == pii{bmed[1], bmed[0]}) {
					eb.erase(eb.begin() + i);
					break;
				}
			}
		}
		amed = {n - 1};
		bmed = {n - 1};
	}

	set<pii> aedge_set;
	rep(i, n - 1) {
		auto [u, v] = ea[i];
		aedge_set.insert(minmax(u, v));
	}

	auto shift = [&](vector<P> &po, P center) {
		auto [x, y, z] = center;
		for(auto &[nx, ny, nz] : po) {
			nx -= x;
			ny -= y;
			nz -= z;
		}
	};

	auto dist = [&](P u, P v) {
		auto [x1, y1, z1] = u;
		auto [x2, y2, z2] = v;
		return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2) + pow(z2 - z1, 2));
	};

	auto am = amed[0], bm = bmed[0];


	shift(pa, pa[am]);
	shift(pb, pb[bm]);


	double ad_min = 1e10, bd_min = 1e10;
	rep(i, n - 1) {
		ad_min = min(ad_min, dist(pa[ea[i].first], pa[ea[i].second]));
		bd_min = min(bd_min, dist(pb[eb[i].first], pb[eb[i].second]));
	}

	debug(ad_min, bd_min);

	auto re_scaling = [&](vector<P> &po, double f) {
		for(auto &[nx, ny, nz] : po) {
			nx *= f;
			ny *= f;
			nz *= f;
		}
	};

	re_scaling(pb, ad_min / bd_min);

	const double eps = 1e-8;

	auto eq0 = [&](double d) -> bool {
		return abs(d) < eps;
	};

	auto eq = [&](double a, double b) -> bool {
		return abs(a - b) < eps;
	};


	int s = -1,
	    t = -1;
	rep(i, n) {
		if(s != -1) break;
		if(i == am) continue;
		auto [xi, yi, zi] = pa[i];
		auto [xm, ym, zm] = pa[am];
		auto v1 = vector{xi - xm, yi - ym, zi - zm};
		rng(j, i + 1, n) {
			if(j == am) continue;
			auto [xj, yj, zj] = pa[j];
			auto v2 = vector{xj - xm, yj - ym, zj - zm};
			auto cx = v1[1] * v2[2] - v1[2] * v2[1];
			auto cy = v1[2] * v2[0] - v1[0] * v2[2];
			auto cz = v1[0] * v2[1] - v1[1] * v2[0];
			if(eq0(cx) and eq0(cy) and eq0(cz)) {
				continue;
			}
			s = i, t = j;
			break;
		}
	}


	auto rotx = [&](P p, double theta) {
		auto [x, y, z] = p;
		double rx, ry, rz;
		rx = x;
		ry = y * cos(theta) - z * sin(theta);
		rz = y * sin(theta) + z * cos(theta);
		return P{rx, ry, rz};
	};

	auto rotz = [&](P p, double theta) {
		auto [x, y, z] = p;
		double rx, ry, rz;
		rx = x * cos(theta) - y * sin(theta);
		ry = x * sin(theta) + y * cos(theta);
		rz = z;
		return P{rx, ry, rz};
	};

	auto rotate = [&](vector<P> points, int u, int v) {
		auto ret = points;
		auto &[xu, yu, zu] = ret[u];
		if(!eq0(yu) or !eq0(zu)) {
			double theta = atan2(zu, yu);
			for(auto &p : ret) p = rotx(p, -theta);
		}
		assert(eq0(zu));
		if(!eq0(yu) or !eq0(xu)) {
			double theta = atan2(yu, xu);
			for(auto &p : ret) p = rotz(p, -theta);
		}
		auto &[xv, yv, zv] = ret[v];
		if(!eq0(yv) or !eq0(zv)) {
			double theta = atan2(zv, yv);
			for(auto &p : ret) p = rotx(p, -theta);
		}
		return ret;
	};
	debug(s, t);
	if(s == -1) {
		rep(i, n) {
			if(s != -1) break;
			if(i == am) continue;
			rng(j, i + 1, n) {
				if(j == am) continue;
				s = i;
				t = j;
				break;
			}
		}
	}


	pa = rotate(pa, s, t);

	debug("PA");
	for(auto [x, y, z] : pa)
		debug(x, y, z);

	int res = 0;


	rep(i, n) {
		if(i == bm) continue;
		if(!eq(dist(pa[s], pa[am]), dist(pb[bm], pb[i]))) continue;
		// cent
		rep(j, n) {
			if(i == j or j == bm) continue;

			auto rot_pb = rotate(pb, i, j);
			//
			debug("PB");
			debug(i, j, s, t);
			for(auto [x, y, z] : rot_pb)
				debug(x, y, z);


			if(!eq(dist(pa[t], pa[am]), dist(pb[bm], pb[j]))) continue;
			// s -> i, j -> t


			vi match(n, -1);
			bool is_match = true;
			rep(k, n) {
				auto &[xk, yk, zk] = pa[k];
				bool ok = 0;
				rep(l, n) {
					if(match[l] != -1) continue;
					auto &[xl, yl, zl] = rot_pb[l];
					if(eq(xk, xl) and eq(yk, yl) and eq(zk, zl)) {
						ok = 1;
						match[l] = k;
						break;
					}
				}
				if(!ok) {
					is_match = false;
				}
			}
			// 01 13 23 14
			if(!is_match) continue;
			if(match[i] != s or match[j] != t) continue;
			debug(match);
			bool edge_match = true;
			rep(k, n - 1) {
				auto [u, v] = eb[k];
				debug(u, v);
				debug(match[u], match[v]);
				if(!aedge_set.count(minmax(match[u], match[v]))) {
					edge_match = false;
					continue;
				}
			}
			debug(i, j, edge_match);
			if(edge_match) res++;
		}
	}
	cout << res << endl;
}
accuracy: 1
time_ms: 20
memory_kb: 4004
final_rank: 4

code_snippet: #ifdef LOCAL
#define _GLIBCXX_DEBUG
#endif
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using vi = vector<int>;
#define rep(i, x)    for(int i = 0; i < (x); i++)
#define rng(i, l, r) for(int i = (l); i < (r); i++)
#define all(v)       (v).begin(), (v).end()
#define sz(v)        (int)(v).size()

template <class T>
void view(const T &e) {
	cerr << e;
}
template <class T>
void view(const vector<T> &v) {
	if(v.empty()) {
		cerr << "{ }";
		return;
	}
	cerr << "{ ";
	for(const auto &e : v) {
		view(e);
		cerr << ", ";
	}
	cerr << "\b\b] }";
}


void debug_out() {}
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
	view(H);
	cerr << ", ";
	debug_out(T...);
}

#ifdef LOCAL
#define debug(...)                                          \
	do {                                                    \
		cerr << __LINE__ << "[" << #__VA_ARGS__ << "] : ["; \
		debug_out(__VA_ARGS__);                             \
		cerr << "\b\b]\n";                                  \
	} while(0)
#else
#define debug(...) (void(0))
#endif


struct segtree {
	using T = int;
	static constexpr T unit = INT_MAX;
	T f(T a, T b) { return min(a, b); }
	vector<T> s;
	int n;
	segtree(int n = 0, T def = unit) : s(2 * n, def), n(n) {}
	void update(int pos, T val) {
		for(s[pos += n] = val; pos /= 2;) s[pos] = f(s[pos * 2], s[pos * 2 + 1]);
	}
	T query(int b, int e) {
		T ra = unit, rb = unit;
		for(b += n, e += n; b < e; b /= 2, e /= 2) {
			if(b % 2) ra = f(ra, s[b++]);
			if(e % 2) rb = f(s[--e], rb);
		}
		return f(ra, rb);
	}
};


const int mod = 1000000007;
const int inf = 1 << 30;

struct ft {
	vector<ll> s;
	ft(int n) : s(n) {}
	void add(int p, ll c) {
		for(; p < sz(s); p |= p + 1) {
			s[p] += c;
			s[p] %= mod;
		}
	}
	ll query(int p) {
		ll res = 0;
		for(; p > 0; p &= p - 1) {
			res += s[p - 1];
			res %= mod;
		}
		return res;
	}
	ll query(int l, int r) {
		ll ret = query(r) - query(l);
		ret %= mod;
		return ret;
	}
};

using P = tuple<double, double, double>;

int main() {
	int n;
	cin >> n;
	vector<P> pa(n), pb(n);
	vector<vi> a(n), b(n);
	vector<pii> ea(n - 1), eb(n - 1);
	rep(i, n) {
		int x, y, z;
		cin >> x >> y >> z;
		pa[i] = P{x, y, z};
	}
	rep(i, n - 1) {
		int u, v;
		cin >> u >> v;
		--u, --v;
		ea[i] = {u, v};
		a[u].push_back(v);
		a[v].push_back(u);
	}
	rep(i, n) {
		int x, y, z;
		cin >> x >> y >> z;
		pb[i] = P{x, y, z};
	}
	rep(i, n - 1) {
		int u, v;
		cin >> u >> v;
		--u, --v;
		u -= n, v -= n;
		eb[i] = {u, v};
		b[u].push_back(v);
		b[v].push_back(u);
	}


	auto bfs = [&](vector<vi> &g, int s) {
		vector<int> dist(sz(g), inf);
		dist[s] = 0;
		queue<int> que;
		que.push(s);
		while(sz(que)) {
			auto v = que.front();
			que.pop();
			for(auto to : g[v]) {
				if(dist[to] == inf) {
					dist[to] = dist[v] + 1;
					que.push(to);
				}
			}
		}
		return dist;
	};
	auto med = [&](vector<vi> &g) {
		auto d0 = bfs(g, 0);
		int mx = -1, v = -1;
		rep(i, n) {
			if(mx < d0[i]) {
				mx = d0[i];
				v = i;
			}
		}
		auto dv = bfs(g, v);
		mx = -1;
		int u = -1;
		rep(i, n) {
			if(mx < dv[i]) {
				mx = dv[i];
				u = i;
			}
		}
		auto dist = bfs(g, u);
		debug(dist);
		debug(dv);
		vi ret;
		rep(i, n) {
			if(dist[i] + dv[i] == dist[v]) {
				if(dist[v] % 2 == 0) {
					if(dist[i] == dv[i]) {
						ret.push_back(i);
					}
				} else {
					if(abs(dist[i] - dv[i]) == 1) {
						ret.push_back(i);
					}
				}
			}
		}
		return ret;
	};
	vi amed = med(a);
	vi bmed = med(b);
	if(sz(amed) != sz(bmed)) {
		cout << 0 << endl;
		return 0;
	}


	debug(amed);
	debug(bmed);

	if(sz(amed) == 2) {
		n++;
		a.resize(n);
		b.resize(n);
		{
			auto [x1, y1, z1] = pa[amed[0]];
			auto [x2, y2, z2] = pa[amed[1]];
			pa.push_back(P{(x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2});
			a[amed[0]].push_back(n - 1);
			ea.push_back({amed[0], n - 1});
			a[amed[1]].push_back(n - 1);
			ea.push_back({amed[1], n - 1});
			a[n - 1].push_back(amed[0]);
			a[n - 1].push_back(amed[1]);
			rep(i, sz(ea)) {
				if(ea[i] == pii{amed[0], amed[1]} or ea[i] == pii{amed[1], amed[0]}) {
					ea.erase(ea.begin() + i);
					break;
				}
			}
		}
		{
			auto [x1, y1, z1] = pb[bmed[0]];
			auto [x2, y2, z2] = pb[bmed[1]];
			pb.push_back(P{(x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2});
			b[bmed[0]].push_back(n - 1);
			b[bmed[1]].push_back(n - 1);
			eb.push_back({bmed[0], n - 1});
			eb.push_back({bmed[1], n - 1});
			b[n - 1].push_back(bmed[0]);
			b[n - 1].push_back(bmed[1]);
			rep(i, sz(eb)) {
				if(eb[i] == pii{bmed[0], bmed[1]} or eb[i] == pii{bmed[1], bmed[0]}) {
					eb.erase(eb.begin() + i);
					break;
				}
			}
		}
		amed = {n - 1};
		bmed = {n - 1};
	}

	set<pii> aedge_set;
	rep(i, n - 1) {
		auto [u, v] = ea[i];
		aedge_set.insert(minmax(u, v));
	}

	auto shift = [&](vector<P> &po, P center) {
		auto [x, y, z] = center;
		for(auto &[nx, ny, nz] : po) {
			nx -= x;
			ny -= y;
			nz -= z;
		}
	};

	auto dist = [&](P u, P v) {
		auto [x1, y1, z1] = u;
		auto [x2, y2, z2] = v;
		return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2) + pow(z2 - z1, 2));
	};

	auto am = amed[0], bm = bmed[0];


	shift(pa, pa[am]);
	shift(pb, pb[bm]);


	double ad_min = 1e10, bd_min = 1e10;
	rep(i, n - 1) {
		ad_min = min(ad_min, dist(pa[ea[i].first], pa[ea[i].second]));
		bd_min = min(bd_min, dist(pb[eb[i].first], pb[eb[i].second]));
	}

	debug(ad_min, bd_min);

	auto re_scaling = [&](vector<P> &po, double f) {
		for(auto &[nx, ny, nz] : po) {
			nx *= f;
			ny *= f;
			nz *= f;
		}
	};

	re_scaling(pb, ad_min / bd_min);

	const double eps = 1e-8;

	auto eq0 = [&](double d) -> bool {
		return abs(d) < eps;
	};

	auto eq = [&](double a, double b) -> bool {
		return abs(a - b) < eps;
	};


	int s = -1,
	    t = -1;
	rep(i, n) {
		if(s != -1) break;
		if(i == am) continue;
		auto [xi, yi, zi] = pa[i];
		auto [xm, ym, zm] = pa[am];
		auto v1 = vector{xi - xm, yi - ym, zi - zm};
		rng(j, i + 1, n) {
			if(j == am) continue;
			auto [xj, yj, zj] = pa[j];
			auto v2 = vector{xj - xm, yj - ym, zj - zm};
			auto cx = v1[1] * v2[2] - v1[2] * v2[1];
			auto cy = v1[2] * v2[0] - v1[0] * v2[2];
			auto cz = v1[0] * v2[1] - v1[1] * v2[0];
			if(eq0(cx) and eq0(cy) and eq0(cz)) {
				continue;
			}
			s = i, t = j;
			break;
		}
	}


	auto rotx = [&](P p, double theta) {
		auto [x, y, z] = p;
		double rx, ry, rz;
		rx = x;
		ry = y * cos(theta) - z * sin(theta);
		rz = y * sin(theta) + z * cos(theta);
		return P{rx, ry, rz};
	};

	auto rotz = [&](P p, double theta) {
		auto [x, y, z] = p;
		double rx, ry, rz;
		rx = x * cos(theta) - y * sin(theta);
		ry = x * sin(theta) + y * cos(theta);
		rz = z;
		return P{rx, ry, rz};
	};

	auto rotate = [&](vector<P> points, int u, int v) {
		auto ret = points;
		auto &[xu, yu, zu] = ret[u];
		if(!eq0(yu) or !eq0(zu)) {
			double theta = atan2(zu, yu);
			for(auto &p : ret) p = rotx(p, -theta);
		}
		assert(eq0(zu));
		if(!eq0(yu) or !eq0(xu)) {
			double theta = atan2(yu, xu);
			for(auto &p : ret) p = rotz(p, -theta);
		}
		auto &[xv, yv, zv] = ret[v];
		if(!eq0(yv) or !eq0(zv)) {
			double theta = atan2(zv, yv);
			for(auto &p : ret) p = rotx(p, -theta);
		}
		return ret;
	};
	debug(s, t);
	if(s == -1) {
		rep(i, n) {
			if(s != -1) break;
			if(i == am) continue;
			rng(j, i + 1, n) {
				if(j == am) continue;
				s = i;
				t = j;
				break;
			}
		}
	}


	pa = rotate(pa, s, t);

	debug("PA");
	for(auto [x, y, z] : pa)
		debug(x, y, z);

	int res = 0;


	rep(i, n) {
		if(i == bm) continue;
		if(!eq(dist(pa[s], pa[am]), dist(pb[bm], pb[i]))) continue;
		// cent
		rep(j, n) {
			if(i == j or j == bm) continue;

			auto rot_pb = rotate(pb, i, j);
			//
			debug("PB");
			debug(i, j, s, t);
			for(auto [x, y, z] : rot_pb)
				debug(x, y, z);


			if(!eq(dist(pa[t], pa[am]), dist(pb[bm], pb[j]))) continue;
			// s -> i, j -> t


			vi match(n, -1);
			bool is_match = true;
			rep(k, n) {
				auto &[xk, yk, zk] = pa[k];
				bool ok = 0;
				rep(l, n) {
					if(match[l] != -1) continue;
					auto &[xl, yl, zl] = rot_pb[l];
					if(eq(xk, xl) and eq(yk, yl) and eq(zk, zl)) {
						ok = 1;
						match[l] = k;
						break;
					}
				}
				if(!ok) {
					is_match = false;
				}
			}
			// 01 13 23 14
			if(!is_match) continue;
			if(match[i] != s or match[j] != t) continue;
			debug(match);
			bool edge_match = true;
			rep(k, n - 1) {
				auto [u, v] = eb[k];
				debug(u, v);
				debug(match[u], match[v]);
				if(!aedge_set.count(minmax(match[u], match[v]))) {
					edge_match = false;
					continue;
				}
			}
			debug(i, j, edge_match);
			if(edge_match) res++;
		}
	}
	cout << res << endl;
}
accuracy: 1
time_ms: 20
memory_kb: 3988
final_rank: 2

code_snippet: #ifdef LOCAL
#define _GLIBCXX_DEBUG
#endif
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using vi = vector<int>;
#define rep(i, x)    for(int i = 0; i < (x); i++)
#define rng(i, l, r) for(int i = (l); i < (r); i++)
#define all(v)       (v).begin(), (v).end()
#define sz(v)        (int)(v).size()

template <class T>
void view(const T &e) {
	cerr << e;
}
template <class T>
void view(const vector<T> &v) {
	if(v.empty()) {
		cerr << "{ }";
		return;
	}
	cerr << "{ ";
	for(const auto &e : v) {
		view(e);
		cerr << ", ";
	}
	cerr << "\b\b] }";
}


void debug_out() {}
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
	view(H);
	cerr << ", ";
	debug_out(T...);
}

#ifdef LOCAL
#define debug(...)                                          \
	do {                                                    \
		cerr << __LINE__ << "[" << #__VA_ARGS__ << "] : ["; \
		debug_out(__VA_ARGS__);                             \
		cerr << "\b\b]\n";                                  \
	} while(0)
#else
#define debug(...) (void(0))
#endif


struct segtree {
	using T = int;
	static constexpr T unit = INT_MAX;
	T f(T a, T b) { return min(a, b); }
	vector<T> s;
	int n;
	segtree(int n = 0, T def = unit) : s(2 * n, def), n(n) {}
	void update(int pos, T val) {
		for(s[pos += n] = val; pos /= 2;) s[pos] = f(s[pos * 2], s[pos * 2 + 1]);
	}
	T query(int b, int e) {
		T ra = unit, rb = unit;
		for(b += n, e += n; b < e; b /= 2, e /= 2) {
			if(b % 2) ra = f(ra, s[b++]);
			if(e % 2) rb = f(s[--e], rb);
		}
		return f(ra, rb);
	}
};


const int mod = 1000000007;
const int inf = 1 << 30;

struct ft {
	vector<ll> s;
	ft(int n) : s(n) {}
	void add(int p, ll c) {
		for(; p < sz(s); p |= p + 1) {
			s[p] += c;
			s[p] %= mod;
		}
	}
	ll query(int p) {
		ll res = 0;
		for(; p > 0; p &= p - 1) {
			res += s[p - 1];
			res %= mod;
		}
		return res;
	}
	ll query(int l, int r) {
		ll ret = query(r) - query(l);
		ret %= mod;
		return ret;
	}
};

using P = tuple<double, double, double>;

int main() {
	int n;
	cin >> n;
	vector<P> pa(n), pb(n);
	vector<vi> a(n), b(n);
	vector<pii> ea(n - 1), eb(n - 1);
	rep(i, n) {
		int x, y, z;
		cin >> x >> y >> z;
		pa[i] = P{x, y, z};
	}
	rep(i, n - 1) {
		int u, v;
		cin >> u >> v;
		--u, --v;
		ea[i] = {u, v};
		a[u].push_back(v);
		a[v].push_back(u);
	}
	rep(i, n) {
		int x, y, z;
		cin >> x >> y >> z;
		pb[i] = P{x, y, z};
	}
	rep(i, n - 1) {
		int u, v;
		cin >> u >> v;
		--u, --v;
		u -= n, v -= n;
		eb[i] = {u, v};
		b[u].push_back(v);
		b[v].push_back(u);
	}


	auto bfs = [&](vector<vi> &g, int s) {
		vector<int> dist(sz(g), inf);
		dist[s] = 0;
		queue<int> que;
		que.push(s);
		while(sz(que)) {
			auto v = que.front();
			que.pop();
			for(auto to : g[v]) {
				if(dist[to] == inf) {
					dist[to] = dist[v] + 1;
					que.push(to);
				}
			}
		}
		return dist;
	};
	auto med = [&](vector<vi> &g) {
		auto d0 = bfs(g, 0);
		int mx = -1, v = -1;
		rep(i, n) {
			if(mx < d0[i]) {
				mx = d0[i];
				v = i;
			}
		}
		auto dv = bfs(g, v);
		mx = -1;
		int u = -1;
		rep(i, n) {
			if(mx < dv[i]) {
				mx = dv[i];
				u = i;
			}
		}
		auto dist = bfs(g, u);
		debug(dist);
		debug(dv);
		vi ret;
		rep(i, n) {
			if(dist[i] + dv[i] == dist[v]) {
				if(dist[v] % 2 == 0) {
					if(dist[i] == dv[i]) {
						ret.push_back(i);
					}
				} else {
					if(abs(dist[i] - dv[i]) == 1) {
						ret.push_back(i);
					}
				}
			}
		}
		return ret;
	};
	vi amed = med(a);
	vi bmed = med(b);
	if(sz(amed) != sz(bmed)) {
		cout << 0 << endl;
		return 0;
	}


	debug(amed);
	debug(bmed);

	if(sz(amed) == 2) {
		n++;
		a.resize(n);
		b.resize(n);
		{
			auto [x1, y1, z1] = pa[amed[0]];
			auto [x2, y2, z2] = pa[amed[1]];
			pa.push_back(P{(x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2});
			a[amed[0]].push_back(n - 1);
			ea.push_back({amed[0], n - 1});
			a[amed[1]].push_back(n - 1);
			ea.push_back({amed[1], n - 1});
			a[n - 1].push_back(amed[0]);
			a[n - 1].push_back(amed[1]);
			rep(i, sz(ea)) {
				if(ea[i] == pii{amed[0], amed[1]} or ea[i] == pii{amed[1], amed[0]}) {
					ea.erase(ea.begin() + i);
					break;
				}
			}
		}
		{
			auto [x1, y1, z1] = pb[bmed[0]];
			auto [x2, y2, z2] = pb[bmed[1]];
			pb.push_back(P{(x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2});
			b[bmed[0]].push_back(n - 1);
			b[bmed[1]].push_back(n - 1);
			eb.push_back({bmed[0], n - 1});
			eb.push_back({bmed[1], n - 1});
			b[n - 1].push_back(bmed[0]);
			b[n - 1].push_back(bmed[1]);
			rep(i, sz(eb)) {
				if(eb[i] == pii{bmed[0], bmed[1]} or eb[i] == pii{bmed[1], bmed[0]}) {
					eb.erase(eb.begin() + i);
					break;
				}
			}
		}
		amed = {n - 1};
		bmed = {n - 1};
	}

	set<pii> aedge_set;
	rep(i, n - 1) {
		auto [u, v] = ea[i];
		aedge_set.insert(minmax(u, v));
	}

	auto shift = [&](vector<P> &po, P center) {
		auto [x, y, z] = center;
		for(auto &[nx, ny, nz] : po) {
			nx -= x;
			ny -= y;
			nz -= z;
		}
	};

	auto dist = [&](P u, P v) {
		auto [x1, y1, z1] = u;
		auto [x2, y2, z2] = v;
		return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2) + pow(z2 - z1, 2));
	};

	auto am = amed[0], bm = bmed[0];


	shift(pa, pa[am]);
	shift(pb, pb[bm]);


	double ad_min = 1e10, bd_min = 1e10;
	rep(i, n - 1) {
		ad_min = min(ad_min, dist(pa[ea[i].first], pa[ea[i].second]));
		bd_min = min(bd_min, dist(pb[eb[i].first], pb[eb[i].second]));
	}

	debug(ad_min, bd_min);

	auto re_scaling = [&](vector<P> &po, double f) {
		for(auto &[nx, ny, nz] : po) {
			nx *= f;
			ny *= f;
			nz *= f;
		}
	};

	re_scaling(pb, ad_min / bd_min);

	const double eps = 1e-8;

	auto eq0 = [&](double d) -> bool {
		return abs(d) < eps;
	};

	auto eq = [&](double a, double b) -> bool {
		return abs(a - b) < eps;
	};


	int s = -1,
	    t = -1;
	rep(i, n) {
		if(s != -1) break;
		if(i == am) continue;
		auto [xi, yi, zi] = pa[i];
		auto [xm, ym, zm] = pa[am];
		auto v1 = vector{xi - xm, yi - ym, zi - zm};
		rng(j, i + 1, n) {
			if(j == am) continue;
			auto [xj, yj, zj] = pa[j];
			auto v2 = vector{xj - xm, yj - ym, zj - zm};
			auto cx = v1[1] * v2[2] - v1[2] * v2[1];
			auto cy = v1[2] * v2[0] - v1[0] * v2[2];
			auto cz = v1[0] * v2[1] - v1[1] * v2[0];
			if(eq0(cx) and eq0(cy) and eq0(cz)) {
				continue;
			}
			s = i, t = j;
			break;
		}
	}


	auto rotx = [&](P p, double theta) {
		auto [x, y, z] = p;
		double rx, ry, rz;
		rx = x;
		ry = y * cos(theta) - z * sin(theta);
		rz = y * sin(theta) + z * cos(theta);
		return P{rx, ry, rz};
	};

	auto rotz = [&](P p, double theta) {
		auto [x, y, z] = p;
		double rx, ry, rz;
		rx = x * cos(theta) - y * sin(theta);
		ry = x * sin(theta) + y * cos(theta);
		rz = z;
		return P{rx, ry, rz};
	};

	auto rotate = [&](vector<P> points, int u, int v) {
		auto ret = points;
		auto &[xu, yu, zu] = ret[u];
		if(!eq0(yu) or !eq0(zu)) {
			double theta = atan2(zu, yu);
			for(auto &p : ret) p = rotx(p, -theta);
		}
		assert(eq0(zu));
		if(!eq0(yu) or !eq0(xu)) {
			double theta = atan2(yu, xu);
			for(auto &p : ret) p = rotz(p, -theta);
		}
		auto &[xv, yv, zv] = ret[v];
		if(!eq0(yv) or !eq0(zv)) {
			double theta = atan2(zv, yv);
			for(auto &p : ret) p = rotx(p, -theta);
		}
		if(get<0>(ret[u]) < 0) {
			for(auto &[x, y, z] : ret) x *= -1;
		}
		if(get<1>(ret[v]) < 0) {
			for(auto &[x, y, z] : ret) y *= -1;
		}
		return ret;
	};
	debug(s, t);
	if(s == -1) {
		rep(i, n) {
			if(s != -1) break;
			if(i == am) continue;
			rng(j, i + 1, n) {
				if(j == am) continue;
				s = i;
				t = j;
				break;
			}
		}
	}


	pa = rotate(pa, s, t);

	debug("PA");
	for(auto [x, y, z] : pa)
		debug(x, y, z);

	int res = 0;


	rep(i, n) {
		if(i == bm) continue;
		if(!eq(dist(pa[s], pa[am]), dist(pb[bm], pb[i]))) continue;
		// cent
		rep(j, n) {
			if(i == j or j == bm) continue;

			auto rot_pb = rotate(pb, i, j);
			//
			debug("PB");
			debug(i, j, s, t);
			for(auto [x, y, z] : rot_pb)
				debug(x, y, z);


			if(!eq(dist(pa[t], pa[am]), dist(pb[bm], pb[j]))) continue;
			// s -> i, j -> t


			vi match(n, -1);
			bool is_match = true;
			rep(k, n) {
				auto &[xk, yk, zk] = pa[k];
				bool ok = 0;
				rep(l, n) {
					if(match[l] != -1) continue;
					auto &[xl, yl, zl] = rot_pb[l];
					if(eq(xk, xl) and eq(yk, yl) and eq(zk, zl)) {
						ok = 1;
						match[l] = k;
						break;
					}
				}
				if(!ok) {
					is_match = false;
				}
			}
			// 01 13 23 14
			if(!is_match) continue;
			if(match[i] != s or match[j] != t) continue;
			debug(match);
			bool edge_match = true;
			rep(k, n - 1) {
				auto [u, v] = eb[k];
				debug(u, v);
				debug(match[u], match[v]);
				if(!aedge_set.count(minmax(match[u], match[v]))) {
					edge_match = false;
					continue;
				}
			}
			debug(i, j, edge_match);
			if(edge_match) res++;
		}
	}
	cout << res << endl;
}
accuracy: 1
time_ms: 20
memory_kb: 3988
final_rank: 2

code_snippet: #pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            while(1) {
                int nxtpar = -1;
                for (int t: g[par]) {
                    if (t == leaf) continue;
                    nxtpar = t;
                    if (abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                    idx = t;
                    break;
                }
                if (idx != -1 or nxtpar == -1) break;
                leaf = par;
                par = nxtpar;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<point> &B)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 隣接リストがそれぞれ一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iは葉ノード
        int leaf = i;
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        int idx = -1;
        while(1) {
            int nxtpar = -1;
            for (int t: g2[par]) {
                if (t == leaf) continue;
                nxtpar = t;
                auto p = B[par]-B[leaf];
                auto pp = B[t]-B[leaf];
                // 同一直線上にある場合
                if(sgn(p.x*pp.y, pp.x*p.y) == 0 and sgn(p.y*pp.z, pp.y*p.z) == 0 and sgn(p.z*pp.x, pp.z*p.x) == 0) continue;
                auto gB = getTree(B, g2, i, t);
                compare(gA, gB);
                idx = t;
            }
            if (idx != -1 or nxtpar == -1) break;
            leaf = par;
            par = nxtpar;
        }
        if(idx == -1){
            auto gB = getTree(B, g2, i, -1);
            compare(gA, gB);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 80
memory_kb: 3964
final_rank: 5

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

using namespace std;

using ld = long double;

constexpr ld eps = 1e-9;

using point = tuple<ld, ld, ld>;

point operator+(point p, point q) {
    auto [p1, p2, p3] = p;
    auto [q1, q2, q3] = q;
    return { p1 + q1, p2 + q2, p3 + q3 };
}
point operator-(point p, point q) {
    auto [p1, p2, p3] = p;
    auto [q1, q2, q3] = q;
    return { p1 - q1, p2 - q2, p3 - q3 };
}
point operator*(point p, point q) {
    auto [p1, p2, p3] = p;
    auto [q1, q2, q3] = q;
    return { p2 * q3 - p3 * q2, p3 * q1 - p1 * q3, p1 * q2 - p2 * q1 };
}

point operator*(point p, ld x) {
    auto [p1, p2, p3] = p;
    return { p1 * x, p2 * x, p3 * x };
}
point operator*(ld x, point p) {
    auto [p1, p2, p3] = p;
    return { p1 * x, p2 * x, p3 * x };
}
point operator/(point p, ld x) {
    auto [p1, p2, p3] = p;
    return { p1 / x, p2 / x, p3 / x };
}


vector<int> find_centroids(vector<vector<int>> g) {
    int n = g.size();
    vector<int> sub(n, 1);

    vector<int> res;
    auto dfs = [&](auto dfs, int u, int p) -> void {
        bool is_centorid = true;
        for (int v : g[u]) if (v != p) {
            dfs(dfs, v, u);
            sub[u] += sub[v];
            is_centorid &= 2 * sub[v] <= n;
        }
        is_centorid &= 2 * (n - sub[u]) <= n;
        if (is_centorid) {
            res.push_back(u);
        }
    };
    dfs(dfs, 0, -1);
    return res;
}

ld abs(point p) {
    auto [x, y, z] = p;
    return sqrt(x * x + y * y + z * z);
}

bool is_zero(ld x) {
    return abs(x) < eps;
}
bool is_zero(point p) {
    auto [x, y, z] = p;
    return is_zero(x) and is_zero(y) and is_zero(z);
}
bool is_equal(ld x, ld y) {
    return is_zero(x - y);
}
bool is_equal(point x, point y) {
    return is_zero(x - y);
}

bool online(point p, point q, point r) {
    point pq = q - p;
    point pr = r - p;
    return is_zero(pq * pr);
}

bool is_isomorphic(const vector<int>& p, const vector<vector<int>>& g1, const vector<vector<int>>& g2) {
    const int n = p.size();

    vector<vector<int>> h1(n);
    for (int i = 0; i < n; ++i) for (int j : g1[i]) {
        h1[p[i]].push_back(p[j]);
    }
    for (auto& adj : h1) {
        sort(adj.begin(), adj.end());
    }
    return h1 == g2;
}

int sign(ld x) {
    return is_zero(x) ? 0 : x > 0 ? +1 : -1;
}

ld dot(point p, point q) {
    auto [p1, p2, p3] = p;
    auto [q1, q2, q3] = q;
    return p1 * q1 + p2 * q2 + p3 * q3;
}

int main() {
    int n;
    cin >> n;

    vector<point> p1(n);
    for (auto& [x, y, z] : p1) {
        cin >> x >> y >> z;
    }
    vector<vector<int>> g1(n);
    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        g1[u].push_back(v);
        g1[v].push_back(u);
    }

    vector<point> p2(n);
    for (auto& [x, y, z] : p2) {
        cin >> x >> y >> z;
    }
    vector<vector<int>> g2(n);
    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        u -= n + 1, v -= n + 1;
        g2[u].push_back(v);
        g2[v].push_back(u);
    }
    for (int i = 0; i < n; ++i) {
        sort(g2[i].begin(), g2[i].end());
    }

    point s1{}, s2{};
    for (int i = 0; i < n; ++i) {
        s1 = s1 + p1[i];
        s2 = s2 + p2[i];
    }
    s1 = s1 / n;
    s2 = s2 / n;
    for (auto& p : p1) p = p - s1;
    for (auto& p : p2) p = p - s2;

    const auto p2_copy = p2;

    set<vector<int>> ans;

    auto solve = [&](const int c1, const int c2) {
        for (int b1 = 0; b1 < n; ++b1) for (int a1 = 0; a1 < b1; ++a1) {
            if (online(p1[a1], p1[b1], p1[c1])) continue;

            for (int b2 = 0; b2 < n; ++b2) for (int a2 = 0; a2 < n; ++a2) {
                if (a2 == b2 or b2 == c2 or c2 == a2) continue;

                p2 = p2_copy;

                ld k = 0;
                if (not (is_zero(p1[a1]) or is_zero(p2[a2]))) {
                    k = abs(p1[a1]) / abs(p2[a2]);
                } else if (not (is_zero(p1[b1]) or is_zero(p2[b2]))) {
                    k = abs(p1[b1]) / abs(p2[b2]);
                } else if (not (is_zero(p1[c1]) or is_zero(p2[c2]))) {
                    k = abs(p1[c1]) / abs(p2[c2]);
                } else {
                    continue;
                }
                for (auto& p : p2) p = k * p;

                const array<point, 3> t1{ p1[a1], p1[b1], p1[c1] };
                const array<point, 3> t2{ p2[a2], p2[b2], p2[c2] };

                const bool is_equiv = [&] {
                    bool res = true;
                    for (int i = 0; i < 3; ++i) {
                        ld l1 = abs(t1[i] - t1[(i + 1) % 3]);
                        ld l2 = abs(t2[i] - t2[(i + 1) % 3]);
                        res &= is_equal(l1, l2);
                    }
                    return res;
                }();
                if (not is_equiv) continue;

                const point dir1 = (t1[1] - t1[0]) * (t1[2] - t1[0]);
                const point dir2 = (t2[1] - t2[0]) * (t2[2] - t2[0]);

                vector<tuple<array<ld, 3>, int, int>> d1(n), d2(n);
                for (int i = 0; i < n; ++i) {
                    for (int j = 0; j < 3; ++j) {
                        get<0>(d1[i])[j] = abs(p1[i] - t1[j]);
                        get<0>(d2[i])[j] = abs(p2[i] - t2[j]);
                    }
                    get<1>(d1[i]) = sign(dot(dir1, p1[i] - t1[0]));
                    get<1>(d2[i]) = sign(dot(dir2, p2[i] - t2[0]));

                    get<2>(d1[i]) = i;
                    get<2>(d2[i]) = i;
                }

                auto comp = [](const tuple<array<ld, 3>, int, int>& x, const tuple<array<ld, 3>, int, int>& y) {
                    auto [d1, s1, i1] = x;
                    auto [d2, s2, i2] = y;
                    if (s1 != s2) {
                        return s1 < s2;
                    }
                    auto [x1, y1, z1] = d1;
                    auto [x2, y2, z2] = d2;

                    if (not is_equal(x1, x2)) return x1 < x2;
                    if (not is_equal(y1, y2)) return y1 < y2;
                    if (not is_equal(z1, z2)) return z1 < z2;
                    return false;
                };

                sort(d1.begin(), d1.end(), comp);
                sort(d2.begin(), d2.end(), comp);

                bool ok = true;

                vector<int> p(n);
                for (int i = 0; i < n; ++i) {
                    auto [di1, si1, i1] = d1[i];
                    auto [di2, si2, i2] = d2[i];
                    p[i1] = i2;
                    ok &= si1 == si2;
                    for (int j = 0; j < 3; ++j) {
                        ok &= is_equal(di1[j], di2[j]);
                    }
                }

                if (ok and is_isomorphic(p, g1, g2)) {
                    ans.insert(p);
                }
            }

            return;
        }
    };

    bool line = true;
    for (int i = 0; i < n; ++i) for (int j = 0; j < i; ++j) for (int k = 0; k < j; ++k) {
        if (not online(p1[i], p1[j], p1[k])) {
            line = false;
            break;
        }
    }

    if (line) {
        auto edge = [](const vector<point>& p) {
            const int n = p.size();
            vector<int> q(n);
            iota(q.begin(), q.end(), 0);

            auto comp = [](const point& x, const point& y) {
                auto [x1, y1, z1] = x;
                auto [x2, y2, z2] = y;
                if (not is_equal(x1, x2)) return x1 < x2;
                if (not is_equal(y1, y2)) return y1 < y2;
                if (not is_equal(z1, z2)) return z1 < z2;
                return false;
            };

            sort(q.begin(), q.end(), [&](int i, int j) {
                return comp(p[i], p[j]);
            });

            return array<int, 2>{ q.front(), q.back() };
        };

        for (int a1 : edge(p1)) for (int a2 : edge(p2)) {
            vector<pair<ld, int>> d1(n), d2(n);
            for (int i = 0; i < n; ++i) {
                d1[i] = { abs(p1[a1] - p1[i]), i };
                d2[i] = { abs(p2[a2] - p2[i]), i };
            }
            sort(d1.begin(), d1.end());
            sort(d2.begin(), d2.end());

            const int b1 = d1.back().second;
            const int b2 = d2.back().second;

            ld k = 0;
            if (not (is_zero(p1[a1]) or is_zero(p2[a2]))) {
                k = abs(p1[a1]) / abs(p2[a2]);
            } else if (not (is_zero(p1[b1]) or is_zero(p2[b2]))) {
                k = abs(p1[b1]) / abs(p2[b2]);
            } else {
                continue;
            }

            bool ok = true;
            vector<int> p(n);
            for (int i = 0; i < n; ++i) {
                auto [e1, i1] = d1[i];
                auto [e2, i2] = d2[i];
                ok &= is_equal(e1, k * e2);
                p[i1] = i2;
            }

            if (ok and is_isomorphic(p, g1, g2)) {
                ans.insert(p);
            }
        }
    } else {
        vector<int> cs1 = find_centroids(g1);
        vector<int> cs2 = find_centroids(g2);

        for (int c1 : cs1) for (int c2 : cs2) {
            solve(c1, c2);
        }
    }

    cout << ans.size() << endl;
}
accuracy: 1
time_ms: 60
memory_kb: 3636
final_rank: 1

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <set>
#include <assert.h>
using namespace std;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<point> &B)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 隣接リストがそれぞれ一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iは葉ノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, gB);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 4036
final_rank: 12

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <set>
#include <assert.h>
using namespace std;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 隣接リストがそれぞれ一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iは葉ノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, g1, gB, g2);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 4020
final_rank: 11

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <random>
#include <numeric>
#include <set>
using namespace std;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 隣接リストがそれぞれ一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iは葉ノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, g1, gB, g2);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 3980
final_rank: 7

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <random>
#include <numeric>
#include <set>
using namespace std;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 隣接リストがそれぞれ一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iは葉ノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, g1, gB, g2);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 4108
final_rank: 14

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <chrono>
#include <random>
#include <numeric>
#include <set>
using namespace std;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 隣接リストがそれぞれ一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iは葉ノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, g1, gB, g2);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 3980
final_rank: 7

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <chrono>
#include <random>
#include <numeric>
#include <set>
using namespace std;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 隣接リストがそれぞれ一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iは葉ノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, g1, gB, g2);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 3996
final_rank: 9

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <chrono>
#include <random>
#include <numeric>
#include <set>
#include <deque>
#include <stack>
#include <sstream>
#include <utility>
#include <cstring>
#include <unordered_map>
#include <unordered_set>
#include <tuple>
#include <array>
#include <bitset>
using namespace std;

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 木が一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iはリーフノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, g1, gB, g2);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 4004
final_rank: 10

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <chrono>
#include <random>
#include <numeric>
#include <set>
#include <deque>
#include <stack>
#include <sstream>
#include <utility>
#include <cstring>
#include <unordered_map>
#include <unordered_set>
#include <tuple>
#include <array>
#include <bitset>
using namespace std;
typedef long long int ll;
typedef unsigned long long ull;

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
ll myRand(ll B) {
    return (ull)rng() % B;
}
inline double time() {
    return static_cast<long double>(chrono::duration_cast<chrono::nanoseconds>(chrono::steady_clock::now().time_since_epoch()).count()) * 1e-9;
}

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->void{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return;
        }
        // 木が一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return;
        }
        st.insert(Bidx);
    };

    // AとBの比較
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iはリーフノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        for(int j:g2[par]){
            auto gB = getTree(B, g2, i, (i==j?-1:j));
            compare(gA, g1, gB, g2);
        }
    }

    // output
    cout << st.size() << endl;
}
accuracy: 1
time_ms: 90
memory_kb: 4040
final_rank: 13

code_snippet: #pragma GCC optimize("Ofast")
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <cstdio>
#include <ctime>
#include <assert.h>
#include <chrono>
#include <random>
#include <numeric>
#include <set>
#include <deque>
#include <stack>
#include <sstream>
#include <utility>
#include <cstring>
#include <unordered_map>
#include <unordered_set>
#include <tuple>
#include <array>
#include <bitset>
using namespace std;
typedef long long int ll;
typedef unsigned long long ull;

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
ll myRand(ll B) {
    return (ull)rng() % B;
}
inline double time() {
    return static_cast<long double>(chrono::duration_cast<chrono::nanoseconds>(chrono::steady_clock::now().time_since_epoch()).count()) * 1e-9;
}

const double eps = 1e-8;
int sgn(double a){return a<-eps?-1:a>eps?1:0;}
int sgn(double a, double b){return sgn(a-b);}
struct point{
    double x,y,z;
    point() {}
    point(double x,double y,double z): x(x), y(y), z(z) {}
    point operator+ (const point& p){
        return point(x+p.x, y+p.y, z+p.z);
    }
    point operator- (const point& p){
        return point(x-p.x, y-p.y, z-p.z);
    }
    point operator/ (const double d){
        return point(x/d, y/d, z/d);
    }
    double dist(const point& p){
        return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y) + (z-p.z)*(z-p.z));
    }
};

int main(){
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    // input
    int n; cin >> n;
    vector<point> A(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        A[i] = point(x,y,z);
    }
    vector<vector<int>> g1(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x--; y--;
        g1[x].push_back(y);
        g1[y].push_back(x);
    }
    vector<point> B(n);
    for(int i=0;i<n;i++){
        int x,y,z; cin >> x >> y >> z;
        B[i] = point(x,y,z);
    }
    vector<vector<int>> g2(n);
    for(int i=1;i<n;i++){
        int x,y; cin >> x >> y;
        x -= n+1; y -= n+1;
        g2[x].push_back(y);
        g2[y].push_back(x);
    }

    // 木の葉ノードを基準に変形
    auto getTree = [&](vector<point> A, vector<vector<int>> &g, int leaf, int idx = -1)->vector<point>{
        // step1: 葉ノードを原点に平行移動させる
        {
            auto p = A[leaf];
            for(int i=0;i<n;i++){
                A[i] = A[i]-p;
            }
        }
        // step2: 葉ノードと親の距離を1にする
        int par = g[leaf][0];
        {
            double d = A[leaf].dist(A[par]);
            vector<point> nA(n);
            nA[leaf] = A[leaf];
            auto dfs = [&](auto self, int s, int p)->void{
                for(int t:g1[s]){
                    if(t == p) continue;
                    point v = (A[t]-A[s])/d;
                    nA[t] = nA[s]+v;
                    self(self, t, s);
                }
            };
            dfs(dfs, leaf, -1);
            swap(A, nA);
        }
        // step3:z軸中心に回転させ、親ノードをxz平面上へ
        {
            double theta = -atan2(A[par].y, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].y*sin(theta);
                double ny = A[i].x*sin(theta) + A[i].y*cos(theta);
                A[i].x = nx, A[i].y = ny;
            }
        }
        // step4:y軸中心に回転させ、親ノードをx軸上へ
        {
            double theta = -atan2(A[par].z, A[par].x);
            for(int i=0;i<n;i++){
                double nx = A[i].x*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].x*sin(theta) + A[i].z*cos(theta);
                A[i].x = nx, A[i].z = nz;
            }
        }
        // step5:x軸中心に回転させ、親ノードに接続する元の葉以外をy軸上へ
        // idx = -1 ならば任意、それ以外はA[idx]を合わせる
        if(idx == -1){
            for(int t:g[par]){
                if(t == leaf) continue;
                if(abs(A[t].y) < eps and abs(A[t].z) < eps) continue;
                idx = t; break;
            }
        }
        if(idx != -1){
            double theta = -atan2(A[idx].z, A[idx].y);
            for(int i=0;i<n;i++){
                double ny = A[i].y*cos(theta) - A[i].z*sin(theta);
                double nz = A[i].y*sin(theta) + A[i].z*cos(theta);
                A[i].y = ny, A[i].z = nz;
            }
        }
        // cout << leaf << " " << idx << endl;
        // for(int i=0;i<n;i++){
        //     cout << A[i].x << " " << A[i].y << " " << A[i].z << endl;
        // }
        return A;
    };

    int lA = -1;
    for(int i=0;i<n;i++){
        sort(g1[i].begin(), g1[i].end());
        if(g1[i].size() == 1) lA = i;
    }
    assert(lA != -1);
    auto gA = getTree(A, g1, lA);

    // 比較用の関数
    set<vector<int>> st;
    auto compare = [&](vector<point> &A, vector<vector<int>> &g1, vector<point> &B, vector<vector<int>> &g2)->bool{
        vector<int> Bidx(n,-1),Binv(n);
        // A[i] = B[Bidx[i]]
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(sgn(A[i].x, B[j].x) or sgn(A[i].y, B[j].y) or sgn(A[i].z, B[j].z)) continue;
                Bidx[i] = j; Binv[j] = i; break;
            }
            if(Bidx[i] == -1) return false;
        }
        // 木が一致しているか調べる
        for(int i=0;i<n;i++){
            auto v = g1[i], vv = g2[Bidx[i]];
            for(int &j:vv){
                j = Binv[j];
            }
            sort(vv.begin(), vv.end());
            if(v != vv) return false;
        }
        st.insert(Bidx);
        return true;
    };

    // AとBの比較
    int res = 0;
    for(int i=0;i<n;i++){
        if(g2[i].size() != 1) continue;
        // iはリーフノード
        int par = g2[i][0];
        // step5でy軸に合わせるidxを全て試す
        bool done = false;
        for(int j:g2[par]){
            if(j == i) continue;
            // 同一直線上にある場合は除外
            auto p = B[par]-B[i];
            auto pp = B[j]-B[i];
            if(sgn(p.x*pp.y, pp.x*p.y) == 0 and sgn(p.y*pp.z, pp.y*p.z) == 0 and sgn(p.z*pp.x, pp.z*p.x) == 0) continue;
            auto gB = getTree(B, g2, i, j);
            if(compare(gA, g1, gB, g2)) res++;
            done = true;
        }
        if(!done){
            auto gB = getTree(B, g2, i, -1);
            if(compare(gA, g1, gB, g2)) res++;
        }
    }

    // output
    cout << st.size() << endl;
    // cout << res << endl;
}
accuracy: 1
time_ms: 80
memory_kb: 3976
final_rank: 6

code_snippet: #pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")
#include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<queue>
#include<ciso646>
#include<random>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<unordered_map>
#include<unordered_set>
#include<utility>
#include<cassert>
#include<complex>
#include<numeric>
#include<array>
#include<chrono>
using namespace std;

//#define int long long
typedef long long ll;

typedef unsigned long long ul;
typedef unsigned int ui;
constexpr ll mod = 998244353;
//constexpr ll mod = 1000000007;
const ll INF = mod * mod;
typedef pair<int, int>P;

#define rep(i,n) for(int i=0;i<n;i++)
#define per(i,n) for(int i=n-1;i>=0;i--)
#define Rep(i,sta,n) for(int i=sta;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define per1(i,n) for(int i=n;i>=1;i--)
#define Rep1(i,sta,n) for(int i=sta;i<=n;i++)
#define all(v) (v).begin(),(v).end()
typedef pair<ll, ll> LP;

template<typename T>
void chmin(T& a, T b) {
	a = min(a, b);
}
template<typename T>
void chmax(T& a, T b) {
	a = max(a, b);
}
template<typename T>
void cinarray(vector<T>& v) {
	rep(i, v.size())cin >> v[i];
}
template<typename T>
void coutarray(vector<T>& v) {
	rep(i, v.size()) {
		if (i > 0)cout << " "; cout << v[i];
	}
	cout << "\n";
}
ll mod_pow(ll x, ll n, ll m = mod) {
	if (n < 0) {
		ll res = mod_pow(x, -n, m);
		return mod_pow(res, m - 2, m);
	}
	if (abs(x) >= m)x %= m;
	if (x < 0)x += m;
	//if (x == 0)return 0;
	ll res = 1;
	while (n) {
		if (n & 1)res = res * x % m;
		x = x * x % m; n >>= 1;
	}
	return res;
}
struct modint {
	int n;
	modint() :n(0) { ; }
	modint(ll m) {
		if (m < 0 || mod <= m) {
			m %= mod; if (m < 0)m += mod;
		}
		n = m;
	}
	operator int() { return n; }
};
bool operator==(modint a, modint b) { return a.n == b.n; }
bool operator<(modint a, modint b) { return a.n < b.n; }
modint operator+=(modint& a, modint b) { a.n += b.n; if (a.n >= mod)a.n -= mod; return a; }
modint operator-=(modint& a, modint b) { a.n -= b.n; if (a.n < 0)a.n += mod; return a; }
modint operator*=(modint& a, modint b) { a.n = ((ll)a.n * b.n) % mod; return a; }
modint operator+(modint a, modint b) { return a += b; }
modint operator-(modint a, modint b) { return a -= b; }
modint operator*(modint a, modint b) { return a *= b; }
modint operator^(modint a, ll n) {
	if (n == 0)return modint(1);
	modint res = (a * a) ^ (n / 2);
	if (n % 2)res = res * a;
	return res;
}

ll inv(ll a, ll p) {
	return (a == 1 ? 1 : (1 - p * inv(p % a, a)) / a + p);
}
modint operator/(modint a, modint b) { return a * modint(inv(b, mod)); }
modint operator/=(modint& a, modint b) { a = a / b; return a; }
const int max_n = 1 << 20;
modint fact[max_n], factinv[max_n];
void init_f() {
	fact[0] = modint(1);
	for (int i = 0; i < max_n - 1; i++) {
		fact[i + 1] = fact[i] * modint(i + 1);
	}
	factinv[max_n - 1] = modint(1) / fact[max_n - 1];
	for (int i = max_n - 2; i >= 0; i--) {
		factinv[i] = factinv[i + 1] * modint(i + 1);
	}
}
modint comb(int a, int b) {
	if (a < 0 || b < 0 || a < b)return 0;
	return fact[a] * factinv[b] * factinv[a - b];
}
modint combP(int a, int b) {
	if (a < 0 || b < 0 || a < b)return 0;
	return fact[a] * factinv[a - b];
}

ll gcd(ll a, ll b) {
	a = abs(a); b = abs(b);
	if (a < b)swap(a, b);
	while (b) {
		ll r = a % b; a = b; b = r;
	}
	return a;
}
typedef long double ld;
typedef pair<ld, ld> LDP;
const ld eps = 1e-8;
const ld pi = acosl(-1.0);
template<typename T>
void addv(vector<T>& v, int loc, T val) {
	if (loc >= v.size())v.resize(loc + 1, 0);
	v[loc] += val;
}
/*const int mn = 100005;
bool isp[mn];
vector<int> ps;
void init() {
	fill(isp + 2, isp + mn, true);
	for (int i = 2; i < mn; i++) {
		if (!isp[i])continue;
		ps.push_back(i);
		for (int j = 2 * i; j < mn; j += i) {
			isp[j] = false;
		}
	}
}*/

//[,val)
template<typename T>
auto prev_itr(set<T>& st, T val) {
	auto res = st.lower_bound(val);
	if (res == st.begin())return st.end();
	res--; return res;
}

//[val,)
template<typename T>
auto next_itr(set<T>& st, T val) {
	auto res = st.lower_bound(val);
	return res;
}
using mP = pair<modint, modint>;


int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };
//-----------------------------------------

bool adj[200][200];
void solve() {
	int n; cin >> n;
	vector<int> x(2 * n), y(2 * n), z(2 * n);
	rep(i, n)cin >> x[i] >> y[i] >> z[i];
	vector<vector<int>> G(2 * n);
	rep(i, n - 1) {
		int a, b; cin >> a >> b; a--; b--;
		G[a].push_back(b);
		G[b].push_back(a);
		adj[a][b] = adj[b][a] = true;
	}
	Rep(i, n, 2 * n)cin >> x[i] >> y[i] >> z[i];
	rep(i, n - 1) {
		int a, b; cin >> a >> b; a--; b--;
		G[a].push_back(b);
		G[b].push_back(a);
	}
	vector<bool> isgood(n);
	rep(i, n) {
		int chk = i;
		rep(x1, G[chk].size()) {
			Rep(x2, x1 + 1, G[chk].size()) {
				int dx[2], dy[2], dz[2];
				dx[0] = x[G[chk][x1]] - x[chk];
				dy[0] = y[G[chk][x1]] - y[chk];
				dz[0] = z[G[chk][x1]] - z[chk];
				dx[1] = x[G[chk][x2]] - x[chk];
				dy[1] = y[G[chk][x2]] - y[chk];
				dz[1] = z[G[chk][x2]] - z[chk];
				if (dx[0] * dy[1] != dx[1] * dy[0] || dx[0] * dz[1] != dx[1] * dz[0]) {
					isgood[i] = true;
				}
			}
		}
	}
	function<string(int, int)> sdfs = [&](int id, int fr) {
		vector<string> ns;
		for (int to : G[id])if (to != fr) {
			string nex = sdfs(to, id);
			ns.push_back(nex);
		}
		sort(all(ns));
		string res;
		res.push_back('(');
		rep(i, ns.size())res += ns[i];
		res.push_back(')');
		return res;
	};
	int chk = -1;
	int mi = mod;
	function<int(int, int)> dfs = [&](int id, int fr) {
		int res = 1;
		int ma = 0;
		for (int to : G[id])if (to != fr) {
			int val = dfs(to, id);
			chmax(ma, val);
			res += val;
		}
		int cur = max(ma, n - res);
		if (isgood[id] && cur < mi) {
			mi = cur;
			chk = id;
		}
		return res;
	};
	dfs(0,-1);
	int obj[3];
	if (chk < 0) {
		//
		obj[0] = 0;
		obj[1] = G[0][0];
		obj[2] = 0;
	}
	else {
		obj[0] = chk;
		rep(x1, G[chk].size()) {
			Rep(x2, x1 + 1, G[chk].size()) {
				int dx[2], dy[2], dz[2];
				dx[0] = x[G[chk][x1]] - x[chk];
				dy[0] = y[G[chk][x1]] - y[chk];
				dz[0] = z[G[chk][x1]] - z[chk];
				dx[1] = x[G[chk][x2]] - x[chk];
				dy[1] = y[G[chk][x2]] - y[chk];
				dz[1] = z[G[chk][x2]] - z[chk];
				if (dx[0] * dy[1] != dx[1] * dy[0] || dx[0] * dz[1] != dx[1] * dz[0]) {
					obj[1] = G[chk][x1];
					obj[2] = G[chk][x2];
				}
			}
		}
	}
	//cout << obj[0] << " " << obj[1] << " " << obj[2] << "\n";
	vector<ld> cx(n), cy(n), cz(n);
	rep(i, n) {
		cx[i] = x[i];
		cy[i] = y[i];
		cz[i] = z[i];
	}
	rep(i, n)if (i != obj[0]) {
		cx[i] -= cx[obj[0]];
		cy[i] -= cy[obj[0]];
		cz[i] -= cz[obj[0]];
	}
	cx[obj[0]] = cy[obj[0]] = cz[obj[0]] = 0;
	//erase z
	if (abs(cz[obj[1]]) > eps) {
		ld t = atan2(cz[obj[1]], cy[obj[1]]);
		t *= -1;
		rep(i, n) {
			ld ny = cy[i] * cos(t) - cz[i] * sin(t);
			ld nz = cy[i] * sin(t) + cz[i] * cos(t);
			cy[i] = ny, cz[i] = nz;
		}
	}
	//erase y
	if (true) {
		ld t = atan2(cy[obj[1]], cx[obj[1]]);
		t *= -1;
		rep(i, n) {
			ld nx = cx[i] * cos(t) - cy[i] * sin(t);
			ld ny = cx[i] * sin(t) + cy[i] * cos(t);
			cx[i] = nx, cy[i] = ny;
		}
	}
	if (abs(cy[obj[2]]) > eps || abs(cz[obj[2]]) > eps) {
		ld t = atan2(cz[obj[2]], cy[obj[2]]);
		t *= -1;
		rep(i, n) {
			ld ny = cy[i] * cos(t) - cz[i]*sin(t);
			ld nz = cy[i] * sin(t) + cz[i] * cos(t);
			cy[i] = ny;
			cz[i] = nz;
		}
	}
	auto memox = cx, memoy = cy, memoz = cz;
	int memo[3];
	rep(i, 3)memo[i] = obj[i];
	//coutarray(memox);
	//coutarray(memoy);
	//coutarray(memoz);
	ld len = cx[obj[1]];
	string ns = sdfs(obj[0],-1);
	int ans = 0;
	Rep(r, n, 2 * n) {
		string curs = sdfs(r, -1);
		if (ns == curs) {
			//cout << "hello " << chk << " "<<r<<"\n";
			vector<vector<int>> objs;
			if (chk < 0) {
				for (int to : G[r]) {
					objs.push_back({ r - n,to - n,r-n });
				}
			}
			else {
				rep(i, G[r].size()) {
					rep(j, G[r].size()) {
						if (i == j)continue;
						objs.push_back({ r - n,G[r][i] - n,G[r][j] - n });
					}
				}
			}
			for (auto vobj : objs) {
				int obj[3];
				rep(i, 3)obj[i] = vobj[i];
				vector<ld> cx(n), cy(n), cz(n);
				rep(i, n) {
					cx[i] = x[i + n];
					cy[i] = y[i + n];
					cz[i] = z[i + n];
				}
				rep(i, n)if (i != obj[0]) {
					cx[i] -= cx[obj[0]];
					cy[i] -= cy[obj[0]];
					cz[i] -= cz[obj[0]];
				}
				cx[obj[0]] = cy[obj[0]] = cz[obj[0]] = 0;
				//erase z
				if (abs(cz[obj[1]]) > eps) {
					ld t = atan2(cz[obj[1]], cy[obj[1]]);
					t *= -1;
					rep(i, n) {
						ld ny = cy[i] * cos(t) - cz[i] * sin(t);
						ld nz = cy[i] * sin(t) + cz[i] * cos(t);
						cy[i] = ny, cz[i] = nz;
					}
				}
				//erase y
				if (true) {
					ld t = atan2(cy[obj[1]], cx[obj[1]]);
					t *= -1;
					rep(i, n) {
						ld nx = cx[i] * cos(t) - cy[i] * sin(t);
						ld ny = cx[i] * sin(t) + cy[i] * cos(t);
						cx[i] = nx, cy[i] = ny;
					}
				}
				if (abs(cy[obj[2]]) > eps || abs(cz[obj[2]]) > eps) {
					ld t = atan2(cz[obj[2]], cy[obj[2]]);
					t *= -1;
					rep(i, n) {
						ld ny = cy[i] * cos(t) - cz[i] * sin(t);
						ld nz = cy[i] * sin(t) + cz[i] * cos(t);
						cy[i] = ny;
						cz[i] = nz;
					}
				}
				ld coef = len / cx[obj[1]];
				//cout << "/ " << coef << "\n";
				rep(i, n) {
					cx[i] *= coef;
					cy[i] *= coef;
					cz[i] *= coef;
				}
				int ss = 0;
				vector<int> trans(n);
				rep(i, n)rep(j, n) {
					ld dif = abs(memox[i] - cx[j]) + abs(memoy[i] - cy[j]) + abs(memoz[i] - cz[j]);
					if (dif < eps) {
						ss++;
						trans[j] = i;
					}
				}
				//cout << "?? " << ss << "\n";
				//coutarray(cx);
				//coutarray(cy);
				//coutarray(cz);
				if (ss == n) {
					bool valid = true;
					Rep(i, n, 2 * n)for (int to : G[i]) {
						int x = trans[i - n];
						int y = trans[to - n];
						if (!adj[x][y])valid = false;
					}
					rep(i, 3)if (trans[obj[i]] != memo[i])valid = false;
					if(valid)
					ans++;
				}
			}
		}
	}
	cout << ans << "\n";
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout << fixed << setprecision(10);
	//init_f();
	//init();
	//while(true)
	//expr();
	//int t; cin >> t; rep(i, t)
	solve();
	return 0;
}
accuracy: 1
time_ms: 3000
memory_kb: 14556
final_rank: 17

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

using ll=long long;
#define int ll

#define rng(i,a,b) for(int i=int(a);i<int(b);i++)
#define rep(i,b) rng(i,0,b)
#define gnr(i,a,b) for(int i=int(b)-1;i>=int(a);i--)
#define per(i,b) gnr(i,0,b)
#define pb push_back
#define eb emplace_back
#define a first
#define b second
#define bg begin()
#define ed end()
#define all(x) x.bg,x.ed
#ifdef LOCAL
#define dmp(x) cerr<<__LINE__<<" "<<#x<<" "<<x<<endl
#else
#define dmp(x) void(0)
#endif

template<class t,class u> void chmax(t&a,u b){if(a<b)a=b;}
template<class t,class u> void chmin(t&a,u b){if(b<a)a=b;}

template<class t> using vc=vector<t>;
template<class t> using vvc=vc<vc<t>>;

using pi=pair<int,int>;
using vi=vc<int>;

template<class t,class u>
ostream& operator<<(ostream& os,const pair<t,u>& p){
	return os<<"{"<<p.a<<","<<p.b<<"}";
}

template<class t> ostream& operator<<(ostream& os,const vc<t>& v){
	os<<"{";
	for(auto e:v)os<<e<<",";
	return os<<"}";
}

template<class t>
t sq(t x){
	return x*x;
}


using ld=long double;
const ld eps=1e-10;
int sgn(ld a){return a<-eps?-1:(a>eps?1:0);}

struct pos{
	ld x,y,z;
	ld length(){
		return sqrt(length2());
	}
	ld length2(){
		return sq(x)+sq(y)+sq(z);
	}
	pos operator-(pos r)const{
		return {x-r.x,y-r.y,z-r.z};
	}
	bool operator<(pos r)const{
		return make_tuple(sgn(x-r.x),sgn(y-r.y),sgn(z-r.z))<make_tuple(0,0,0);
	}
	bool operator==(pos r)const{
		return make_tuple(sgn(x-r.x),sgn(y-r.y),sgn(z-r.z))==make_tuple(0,0,0);
	}
	pos operator/(ld r)const{
		return pos{x/r,y/r,z/r};
	}
};

ostream& operator<<(ostream& os,pos p){
	return os<<"{"<<p.x<<","<<p.y<<","<<p.z<<"}";
}

ld dot(pos a,pos b){
	return a.x*b.x+a.y*b.y+a.z*b.z;
}

pos cross(pos a,pos b){
	return {
		a.z*b.y-a.y*b.z,
		a.x*b.z-a.z*b.x,
		a.y*b.x-a.x*b.y,
	};
}

bool collinear(pos a,pos b,pos c){
	return sgn(cross(b-a,c-a).length2())==0;
}

struct ysp{
	vc<pos> ps;
	vc<pi> es;
	bool col;
	int n;
	void init(int nn,int k){
		n=nn;
		rep(i,n){
			ld x,y,z;cin>>x>>y>>z;
			ps.pb(pos{x,y,z});
		}
		rep(_,n-1){
			int a,b;cin>>a>>b;
			a--;b--;
			a-=k*n;
			b-=k*n;
			es.eb(a,b);
			es.eb(b,a);
		}
		col=true;
		rep(i,es.size())rep(j,es.size())if(i!=j){
			if(es[i].a==es[j].a){
				dmp(es[i]);
				dmp(es[j]);
				if(!collinear(ps[es[i].a],ps[es[i].b],ps[es[j].b])){
					col=false;
					break;
				}
			}
		}
	}
	pair<vc<pos>,vc<pi>> waf(int r,int s,int t){
		pos rs=ps[s]-ps[r];
		ld len=rs.length();
		rs=rs/len;
		pos rt=(ps[t]-ps[r])/len;
		pos nr=cross(rs,rt);
		dmp(nr);
		vc<pos> z;
		rep(i,n){
			pos w=(ps[i]-ps[r])/len;
			z.pb(pos{dot(rs,w),dot(rt,w),dot(nr,w)});
		}
		vc<pos> srt=z;
		sort(all(srt));
		vc<pi> tmp;
		for(auto e:es){
			if(e.a<e.b){
				int a=lower_bound(all(srt),z[e.a])-srt.bg;
				int b=lower_bound(all(srt),z[e.b])-srt.bg;
				if(a>b)swap(a,b);
				tmp.eb(a,b);
			}
		}
		sort(all(tmp));
		dmp(r);
		dmp(s);
		dmp(t);
		dmp(srt);
		dmp(tmp);
		return {srt,tmp};
	}
	pair<vc<pos>,vc<pi>> getone(){
		rep(i,es.size())rep(j,es.size())if(i!=j){
			if(es[i].a==es[j].a){
				if(col||!collinear(ps[es[i].a],ps[es[i].b],ps[es[j].b])){
					dmp(es[i].a);
					dmp(es[i].b);
					dmp(es[j].b);
					return waf(es[i].a,es[i].b,es[j].b);
				}
			}
		}
	}
	int enumcheck(pair<vc<pos>,vc<pi>> tar){
		int ans=0;
		rep(i,es.size())rep(j,es.size())if(i!=j){
			if(es[i].a==es[j].a){
				if(col||!collinear(ps[es[i].a],ps[es[i].b],ps[es[j].b])){
					auto cur=waf(es[i].a,es[i].b,es[j].b);
					if(cur==tar)
						ans++;
				}
			}
		}
		return ans;
	}
};

signed main(){
	cin.tie(0);
	ios::sync_with_stdio(0);
	cout<<fixed<<setprecision(20);
	
	int n;cin>>n;
	ysp y[2];
	rep(k,2)
		y[k].init(n,k);
	
	int ans=0;
	if(y[0].col!=y[1].col){
	}else{
		auto base=y[0].getone();
		dmp(base);
		ans=y[1].enumcheck(base);
	}
	cout<<ans<<endl;
}
accuracy: 1
time_ms: 2710
memory_kb: 3408
final_rank: 16