Problem Description
在一棵树上,树的边权为 111 , SerenadeSerenadeSerenade 和 RhapsodyRhapsodyRhapsodya 从各自的起点 SaS_aSa , SbS_bSb 向着终点 TaT_aTa , TbT_bTb 做往返运动。
求 SerenadeSerenadeSerenade 和 RhapsoyaRhapsoyaRhapsoya 在节点上的最早相遇节点。
Input
第一行输入一个整数 ttt (1≤t≤500)(1 \le t \le 500)(1≤t≤500) ,表示测试组数。
接下来一行输入两个整数 n,mn,mn,m (2≤n,m≤3×103)(2 \le n,m \le 3 \times 10^3)(2≤n,m≤3×103),表示的树的节点个数和查询次数。
接下来 n−1n-1n−1 行每行输入两个整数 u,vu,vu,v (1≤u,v≤n,u≠v)(1 \le u,v \le n,u\not= v)(1≤u,v≤n,u=v) ,表示 u,vu,vu,v 之间存在一条边。
接下来 mmm 每行输入四个整数 Sa,Ta,Sb,TbS_a,T_a,S_b,T_bSa,Ta,Sb,Tb (1≤Sa,Ta,Sb,Tb≤n,Sa≠Ta,Sb≠Tb)(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) ,表示 SerenadeSerenadeSerenade 和 RhapsodyRhapsodyRhapsodya 各自的起点和终点。
数据保证不超过 202020 组 n,mn,mn,m 超过 400400400 。
Output
对于每次查询,输出对应节点编号,如果不会相遇则输出 −1-1−1 。
Solution
下文中 SerenadeSerenadeSerenade 和 RhapsoyaRhapsoyaRhapsoya 都视作 aaa , bbb 。
首先如果 aaa 和 bbb 要相交,那么 aaa 起点到终点的路径必然和 bbb 起点到终点的路径相交。而对于相交路径,由于 nnn 和 mmm 只有 3×1033 \times 10^33×103 ,我们可以直接暴力在 aaa 和 bbb 的路径上的点各自 +1+1+1 ,那么此时我们只需考虑路径上的权值为 222 的点即可。
那么对于一个点是否能相遇以及求 aaa 和 bbb 的最早相遇步数,我们进行分类讨论,设 al0,al1al_0,al_1al0,al1 和 bl0,bl1bl_0,bl_1bl0,bl1 为 aaa 和 bbb 起点和终点到交点距离,k1,k2∈N+k_1,k_2 \in N^+k1,k2∈N+,发现相遇只有 444 种情况:
· aaa 从起点走出与 bbb 从起点走出相遇。 2k1×(al0+al1)+al0=2k2×(bl0+bl1)+bl02k_1 \times (al_0+al_1) + al_0 = 2k_2 \times (bl_0+bl_1) + bl_02k1×(al0+al1)+al0=2k2×(bl0+bl1)+bl0。
· aaa 从起点走出与 bbb 从终点走出相遇。 2k1×(al0+al1)+al0=(2k2−1)×(bl0+bl1)+bl12k_1 \times (al_0+al_1) + al_0 = (2k_2-1) \times (bl_0+bl_1) + bl_12k1×(al0+al1)+al0=(2k2−1)×(bl0+bl1)+bl1。
· aaa 从终点走出与 bbb 与起点走出相遇。 (2k1−1)×(al0+al1)+al1=2k2×(bl0+bl1)+bl0(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。
· aaa 从终点走出与 bbb 与终点走出相遇。 (2k1−1)×(al0+al1)+al1=(2k2−1)×(bl0+bl1)+bl1(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。
对于如何计算最少步数和是否相交,我们只需 exgcdexgcdexgcd 算出 k1k_1k1 和 k2k_2k2 的值,如何算对应最小整数即可。
复杂度分析:O(20nmlogn)O(20nmlogn)O(20nmlogn) ,表面一算复杂度甚至可能会到 2×1092 \times 10^92×109,但是观察到 exgcdexgcdexgcd 实际并不会达到 lognlognlogn ,虽然还带有可能 555 倍的常数(?),但是还是能神秘地通过此题。。。
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();
}