Problem Description
在一棵树上,树的边权为 1 1 1 , S e r e n a d e Serenade Serenade 和 R h a p s o d y Rhapsody Rhapsodya 从各自的起点 S a S_a Sa , S b S_b Sb 向着终点 T a T_a Ta , T b T_b Tb 做往返运动。
求 S e r e n a d e Serenade Serenade 和 R h a p s o y a Rhapsoya Rhapsoya 在节点上的最早相遇节点。
Input
第一行输入一个整数 t t t ( 1 ≤ t ≤ 500 ) (1 \le t \le 500) (1≤t≤500) ,表示测试组数。
接下来一行输入两个整数 n , m n,m n,m ( 2 ≤ n , m ≤ 3 × 1 0 3 ) (2 \le n,m \le 3 \times 10^3) (2≤n,m≤3×103),表示的树的节点个数和查询次数。
接下来 n − 1 n-1 n−1 行每行输入两个整数 u , v u,v u,v ( 1 ≤ u , v ≤ n , u ≠ v ) (1 \le u,v \le n,u\not= v) (1≤u,v≤n,u=v) ,表示 u , v u,v u,v 之间存在一条边。
接下来 m m m 每行输入四个整数 S a , T a , S b , T b S_a,T_a,S_b,T_b Sa,Ta,Sb,Tb ( 1 ≤ S a , T a , S b , T b ≤ n , S a ≠ T a , S b ≠ T b ) (1 \le S_a,T_a,S_b,T_b \le n,S_a \not= T_a,S_b \not= T_b) (1≤Sa,Ta,Sb,Tb≤n,Sa=Ta,Sb=Tb) ,表示 S e r e n a d e Serenade Serenade 和 R h a p s o d y Rhapsody Rhapsodya 各自的起点和终点。
数据保证不超过 20 20 20 组 n , m n,m n,m 超过 400 400 400 。
Output
对于每次查询,输出对应节点编号,如果不会相遇则输出 − 1 -1 −1 。
Solution
下文中 S e r e n a d e Serenade Serenade 和 R h a p s o y a Rhapsoya Rhapsoya 都视作 a a a , b b b 。
首先如果 a a a 和 b b b 要相交,那么 a a a 起点到终点的路径必然和 b b b 起点到终点的路径相交。而对于相交路径,由于 n n n 和 m m m 只有 3 × 1 0 3 3 \times 10^3 3×103 ,我们可以直接暴力在 a a a 和 b b b 的路径上的点各自 + 1 +1 +1 ,那么此时我们只需考虑路径上的权值为 2 2 2 的点即可。
那么对于一个点是否能相遇以及求 a a a 和 b b b 的最早相遇步数,我们进行分类讨论,设 a l 0 , a l 1 al_0,al_1 al0,al1 和 b l 0 , b l 1 bl_0,bl_1 bl0,bl1 为 a a a 和 b b b 起点和终点到交点距离, k 1 , k 2 ∈ N + k_1,k_2 \in N^+ k1,k2∈N+,发现相遇只有 4 4 4 种情况:
· a a a 从起点走出与 b b b 从起点走出相遇。 2 k 1 × ( a l 0 + a l 1 ) + a l 0 = 2 k 2 × ( b l 0 + b l 1 ) + b l 0 2k_1 \times (al_0+al_1) + al_0 = 2k_2 \times (bl_0+bl_1) + bl_0 2k1×(al0+al1)+al0=2k2×(bl0+bl1)+bl0。
· a a a 从起点走出与 b b b 从终点走出相遇。 2 k 1 × ( a l 0 + a l 1 ) + a l 0 = ( 2 k 2 − 1 ) × ( b l 0 + b l 1 ) + b l 1 2k_1 \times (al_0+al_1) + al_0 = (2k_2-1) \times (bl_0+bl_1) + bl_1 2k1×(al0+al1)+al0=(2k2−1)×(bl0+bl1)+bl1。
· a a a 从终点走出与 b b b 与起点走出相遇。 ( 2 k 1 − 1 ) × ( a l 0 + a l 1 ) + a l 1 = 2 k 2 × ( b l 0 + b l 1 ) + b l 0 (2k_1-1) \times (al_0+al_1) + al_1 = 2k_2 \times (bl_0+bl_1) + bl_0 (2k1−1)×(al0+al1)+al1=2k2×(bl0+bl1)+bl0。
· a a a 从终点走出与 b b b 与终点走出相遇。 ( 2 k 1 − 1 ) × ( a l 0 + a l 1 ) + a l 1 = ( 2 k 2 − 1 ) × ( b l 0 + b l 1 ) + b l 1 (2k_1-1) \times (al_0+al_1) + al_1 = (2k_2-1) \times (bl_0+bl_1) + bl_1 (2k1−1)×(al0+al1)+al1=(2k2−1)×(bl0+bl1)+bl1。
对于如何计算最少步数和是否相交,我们只需 e x g c d exgcd exgcd 算出 k 1 k_1 k1 和 k 2 k_2 k2 的值,如何算对应最小整数即可。
复杂度分析: O ( 20 n m l o g n ) O(20nmlogn) O(20nmlogn) ,表面一算复杂度甚至可能会到 2 × 1 0 9 2 \times 10^9 2×109,但是观察到 e x g c d exgcd exgcd 实际并不会达到 l o g n logn logn ,虽然还带有可能 5 5 5 倍的常数(?),但是还是能神秘地通过此题。。。
Code
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 3e3 + 10, M = 6e3 + 10;
int meet_point, step;//最优相遇交点和步数
vector<int>a(2), b(2), al(2), bl(2);
int acs[N];
int head[N], e[M], ne[M], idx;
int fa[N], dep[N], siz[N], wson[N], top[N];
void clear(int n) {
idx = 0;
for (int i = 0; i <= n + 5; i++) head[i] = -1, wson[i] = 0;
}
inline void addedge(int a, int b) {
e[idx] = b, ne[idx] = head[a], head[a] = idx++;
}
int exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
//树剖LCA
void dfs1(int u, int father) {
dep[u] = dep[father] + 1, fa[u] = father, siz[u] = 1;
for (int i = head[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == father)continue;
dfs1(v, u);
if (siz[v] > siz[wson[u]])wson[u] = v;
siz[u] += siz[v];
}
}
void dfs2(int u, int chead) {
top[u] = chead;
if (wson[u])dfs2(wson[u], chead);
for (int i = head[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == fa[u] || v == wson[u])continue;
dfs2(v, v);
}
}
int lca(int a, int b) {
while (top[a] != top[b]) {
if (dep[top[a]] < dep[top[b]])b = fa[top[b]];
else a = fa[top[a]];
}
return dep[a] < dep[b] ? a : b;
}
void add_point(int u, int father) {//在u到father路径上的点+1
while (u != father) {
acs[u]++;
u = fa[u];
}
}
int dis(int x, int y) {//求x到y的距离
int anc = lca(x, y);
return dep[x] + dep[y] - 2 * dep[anc];
}
void judge_meet(int cross, int left, int right) {//判断是否相遇并统计答案
int x0, y0, c, d, dx, dy;
int lena = 2 * (al[0] + al[1]), lenb = 2 * (bl[0] + bl[1]);
d = exgcd(lena, lenb, x0, y0);
dx = lenb / d, dy = lena / d;
c = right - left;
if (c % d == 0) {
int x = x0 * c / d, y = y0 * c / d;
x = (x % dx + dx) % dx;
y = (y % dy + dy) % dy;
y -= dy;
int val = min(lena * x + left, -lenb * y + right);
if (step > val)meet_point = cross, step = val;
}
}
void get_ans(int cross) {
al[0] = dis(a[0], cross), al[1] = dis(a[1], cross);//al[0],al[1]表示a点起点终点到交点的距离
bl[0] = dis(b[0], cross), bl[1] = dis(b[1], cross);//bl[0],bl[1]表示b点起点终点到交点的距离
judge_meet(cross, al[0], bl[0]);
judge_meet(cross, al[0], bl[0] + 2 * bl[1]);
judge_meet(cross, al[0] + 2 * al[1], bl[0] + 2 * bl[1]);
judge_meet(cross, al[0] + 2 * al[1], bl[0]);
}
void solve() {
int n, m;
cin >> n >> m;
clear(n);
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
addedge(u, v), addedge(v, u);
}
dfs1(1, 0);
dfs2(1, 1);
while (m--) {
meet_point = -1;
step = 2e9;
for (int i = 1; i <= n; i++)acs[i] = 0;
cin >> a[0] >> a[1] >> b[0] >> b[1];
int x = lca(a[0], a[1]), y = lca(b[0], b[1]);
add_point(a[0], x), add_point(a[1], x), acs[x]++;
add_point(b[0], y), add_point(b[1], y), acs[y]++;
for (int i = 1; i <= n; i++) if (acs[i] >= 2)get_ans(i);
cout << meet_point << endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)solve();
}