#include <bits/stdc++.h>
using namespace std;
using ll = long long;
/*
QUESTION IN SIMPLE WORDS
------------------------
Each node in a tree has an integer value.
For a path between nodes u and v:
- collect all values on that path
- find the smallest prime number that does NOT divide all of them
Important observation:
A prime divides every number on the path
if and only if that prime divides the GCD of the path.
So the real task becomes:
1) compute GCD of values on the path
2) find the smallest prime that does not divide that GCD
------------------------------------------------------------
BRUTE FORCE IDEA
------------------------------------------------------------
For each query (u, v):
- find the full path from u to v
- compute gcd of all values on that path
- check primes 2, 3, 5, 7, ...
------------------------------------------------------------
WHY BRUTE FORCE IS TOO SLOW
------------------------------------------------------------
If each query walks the tree directly, one query can take O(n).
For many queries, that becomes too slow.
------------------------------------------------------------
OPTIMIZED IDEA
------------------------------------------------------------
Use binary lifting.
Binary lifting is normally used to jump upward in a tree quickly.
Here we also store gcd information while jumping upward.
Then for a query:
- bring both nodes to same depth
- lift both until they meet
- combine gcd values along the way
At the end we get the gcd of the whole path.
Then we return the first prime that does not divide it.
*/
struct PathPrimeQuery {
int n, LOG;
vector<ll> val;
vector<vector<int>> g;
vector<int> depth;
// up[k][u] = 2^k-th ancestor of u
vector<vector<int>> up;
// gup[k][u] = gcd of values along that jump
vector<vector<ll>> gup;
PathPrimeQuery(int n, const vector<ll>& val) : n(n), val(val) {
g.assign(n + 1, {});
depth.assign(n + 1, 0);
LOG = 1;
while ((1 << LOG) <= n) LOG++;
up.assign(LOG, vector<int>(n + 1, 0));
gup.assign(LOG, vector<ll>(n + 1, 0));
}
void addEdge(int u, int v) {
g[u].push_back(v);
g[v].push_back(u);
}
void dfs(int u, int p) {
up[0][u] = p;
// For one upward jump:
// if parent does not exist, just use val[u]
// otherwise gcd(val[u], val[parent])
if (p == 0) gup[0][u] = val[u];
else gup[0][u] = __gcd(val[u], val[p]);
for (int k = 1; k < LOG; k++) {
int mid = up[k - 1][u];
up[k][u] = up[k - 1][mid];
// Combine two half jumps
gup[k][u] = __gcd(gup[k - 1][u], gup[k - 1][mid]);
}
for (int v : g[u]) {
if (v == p) continue;
depth[v] = depth[u] + 1;
dfs(v, u);
}
}
void build(int root = 1) {
dfs(root, 0);
}
ll pathGcd(int u, int v) {
ll res = 0;
// Make sure u is deeper
if (depth[u] < depth[v]) swap(u, v);
// Step 1: lift u up until both nodes are at same depth
int diff = depth[u] - depth[v];
for (int k = 0; k < LOG; k++) {
if (diff & (1 << k)) {
res = __gcd(res, gup[k][u]);
u = up[k][u];
}
}
// If both are same node now, include its value and finish
if (u == v) {
res = __gcd(res, val[u]);
return res;
}
// Step 2: lift both until they are just below LCA
for (int k = LOG - 1; k >= 0; k--) {
if (up[k][u] != up[k][v]) {
res = __gcd(res, gup[k][u]);
res = __gcd(res, gup[k][v]);
u = up[k][u];
v = up[k][v];
}
}
// Now u and v are children of LCA
res = __gcd(res, gup[0][u]);
res = __gcd(res, gup[0][v]);
return res;
}
bool isPrime(int x) {
if (x < 2) return false;
for (int d = 2; 1LL * d * d <= x; d++) {
if (x % d == 0) return false;
}
return true;
}
int smallestMissingPrime(ll gVal) {
int p = 2;
while (true) {
if (isPrime(p) && gVal % p != 0) return p;
p++;
}
}
int query(int u, int v) {
ll gVal = pathGcd(u, v);
return smallestMissingPrime(gVal);
}
};
int main() {
int n = 5;
vector<ll> val = {0, 30, 42, 70, 105, 14};
PathPrimeQuery solver(n, val);
solver.addEdge(1, 2);
solver.addEdge(1, 3);
solver.addEdge(2, 4);
solver.addEdge(2, 5);
solver.build(1);
cout << solver.query(4, 5) << '\n';
cout << solver.query(3, 4) << '\n';
return 0;
}