题目链接

这题是 Codeforces Goodbye 2014 的最后一题 CF500G,只是去掉了 \(u \not= x, v \not = v\) 的条件。

官方题解感觉有很多东西说的迷迷瞪瞪,等到自己写的时候就狂 WA 不止。。

前置知识:Exgcd、LCA,没了)

Subtask #1

题目也有明确的提示,一个教师按时刻顺序经过的编号是一个循环节,设 \(D\) 为循环节长度,\(u, v\) 是这条路径,\(d_{u, v}\) 是 \(u, v\) 的树上路径长度,那么:

\[D = \max(1, 2d_{u,v})
\]

即 \(d_{u, v}\) 为 \(0\) 的时候循环节为 \(1\) 需要特判(坑点 1)。

所以,循环节长度是和 \(n\) 同阶的。

对于每一个询问,走 \(g = \operatorname{lcm}(D_1, D_2)\) 次两者便都会重新走到起始位置。

把循环节序列找出来(可以暴力 dfs / 一个个跳 LCA,找都是 \(O(n)\) 的)

然后,枚举答案最多只需要到 \(g\) 时刻,检查一下当前时刻走到的点是否相同就行了。

时间复杂度 \(O(qn^2)\),预计得分 \(10pts\)

Code

#include <iostream>
#include <cstdio> using namespace std; const int N = 200005; int n, m, dep[N], fa[N], A[N << 1], B[N << 1], len1, len2; int d[N << 1]; int head[N], numE = 0; struct E{
int next, v;
} e[N << 1]; void inline add(int u, int v) {
e[++numE] = (E) { head[u], v };
head[u] = numE;
} void dfs(int u) {
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u]) continue;
fa[v] = u, dep[v] = dep[u] + 1;
dfs(v);
}
} void inline work(int x, int y, int a[], int &len) {
int c1 = 1; len = 1;
if (x == y) { len = 1, a[0] = x; return; }
a[0] = x, d[0] = y;
while (x != y) {
if (dep[x] > dep[y]) {
x = fa[x];
if (x != y) a[len++] = x;
} else {
y = fa[y];
if (x != y) d[c1++] = y;
}
}
for (int i = 0; i < c1; i++) a[len + i] = d[c1 - i - 1];
len = len + c1;
for (int i = len - 2; i >= 1; i--) a[len++] = a[i];
} int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
} int main() {
scanf("%d", &n);
for (int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), add(u, v), add(v, u);
dfs(1);
scanf("%d", &m);
while (m--) {
int u, v, x, y; scanf("%d%d%d%d", &u, &v, &x, &y);
work(u, v, A, len1); work(x, y, B, len2);
int ans = -1, g = len1 * len2 / gcd(len1, len2);
for (int i = 0; i < g; i++)
if (A[i % len1] == B[i % len2]) { ans = i; break; }
printf("%d\n", ans);
}
return 0;
}

Subtask #2

即第二个教师原地不动呗,所以答案就是第一个教师第一次经过点 \(x\) 的时间。

先判断 \(x\) 在不在 \((u, v)\) 的简单路径上,即满足:

\[d_{u, x} + d_{x,v} = d_{u, v}
\]

不在就 \(-1\),在答案就是 \(d_{u, x}\)。

\(d\) 预处理一下深度数组,求 LCA 就可以了(用的很慢的倍增)

时间复杂度 \(O((n + q) \log n)\),结合以上算法预计得分 \(20pts\)。

Code

#include <iostream>
#include <cstdio> using namespace std; typedef long long LL; const int N = 200005, L = 18;
const LL INF = 9e18; int n, q, dep[N], fa[N][L]; int head[N], numE = 0; struct E{
int next, v;
} e[N << 1]; void inline add(int u, int v) {
e[++numE] = (E) { head[u], v };
head[u] = numE;
} void dfs(int u) {
for (int i = 1; i < L && fa[u][i - 1]; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u][0]) continue;
fa[v][0] = u, dep[v] = dep[u] + 1;
dfs(v);
}
} int inline lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = L - 1; ~i; i--)
if (dep[x] - (1 << i) >= dep[y]) x = fa[x][i];
if (x == y) return x;
for (int i = L - 1; ~i; i--)
if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
} int inline d(int x, int y) {
return dep[x] + dep[y] - 2 * dep[lca(x, y)];
} LL inline query(int u, int v, int x, int y) {
if (d(u, x) + d(x, v) != d(u, v)) return -1;
return d(u, x);
} int main() {
scanf("%d", &n);
for (int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), add(u, v), add(v, u);
dep[1] = 1, dfs(1);
scanf("%d", &q);
while (q--) {
int u, v, x, y; scanf("%d%d%d%d", &u, &v, &x, &y);
printf("%lld\n", query(u, v, x, y));
}
return 0;
}

Subtask #3

就是两个人在同一条链上往返走呗,小学二年级相遇问题。

若 \(d_{u, v}\) 是偶数,答案就是 \(\frac{d_{u, v}}{2}\)。

否则只可能在边上相遇,答案 \(-1\)。

时间复杂度 \(O((n + q) \log n)\),结合以上算法预计得分 \(30pts\)。

Code

#include <iostream>
#include <cstdio> using namespace std; typedef long long LL; const int N = 200005, L = 18;
const LL INF = 9e18; int n, q, dep[N], fa[N][L]; int head[N], numE = 0; struct E{
int next, v;
} e[N << 1]; void inline add(int u, int v) {
e[++numE] = (E) { head[u], v };
head[u] = numE;
} void dfs(int u) {
for (int i = 1; i < L && fa[u][i - 1]; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u][0]) continue;
fa[v][0] = u, dep[v] = dep[u] + 1;
dfs(v);
}
} int inline lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = L - 1; ~i; i--)
if (dep[x] - (1 << i) >= dep[y]) x = fa[x][i];
if (x == y) return x;
for (int i = L - 1; ~i; i--)
if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
} int inline d(int x, int y) {
return dep[x] + dep[y] - 2 * dep[lca(x, y)];
} LL inline query(int u, int v, int x, int y) {
int D = d(u, v);
return D % 2 == 0 ? D / 2 : -1;
} int main() {
scanf("%d", &n);
for (int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), add(u, v), add(v, u);
dep[1] = 1, dfs(1);
scanf("%d", &q);
while (q--) {
int u, v, x, y; scanf("%d%d%d%d", &u, &v, &x, &y);
printf("%lld\n", query(u, v, x, y));
}
return 0;
}

Subtask #4

既然是一个循环节,我们尝试枚举两个路径都经过的点 \(x\),尝试算出两个教师相遇在 \(x\) 的最小时间,最后取最小值即可。

那么怎么算呢?

先考虑一个老师。

在一次循环节中,有两个时刻 \(t_1 = d_{u, x}, t_2 = d_{u, v} + d_{v, x}\) 是在 \(x\) 的。

考虑 \(D\) 是循环节,所以说所有刚好在 \(x\) 的时刻可以表示为 \(xD + t_1\) 或 \(xD + t_2\) 的形式,其中 \(x\) 为非负整数。

对于另一个教师同理,对于每一个教师,我们找一个这样的数量关系 \(xD+T\),其中 \(D, T\) 是固定的,\(x\) 是非负整数。

这样联立 \(xD_1 + T_1 = yD_2 + T_2 \Leftrightarrow xD_1 - yD_2 = T_2 - T_1\),我们需要找到 \(x, y\) 都是非负整数解中,\(x\) 最小的那组(因为 \(x\) 固定 \(y\) 也就固定了,你让 \(y\) 最小也可),然后 \(xD_1 + T_1\) 就是答案。然后我们枚举四次(每个教师两个时刻关系),一一求最小值就可以了。

问题即变成了求 \(ax - by = c\) 中 \(x, y\) 都为非负整数的解中,最小的 \(x\)。

用 exgcd 就可以了,先把 \(ax + by = c\) 的一组解求出来,然后令 \(b = -b, y = -y\)。

这样就有一组 \(ax - by = c\) 的解了,然后通解的形式就是 \(x =x + k \frac{b}{d},y = y + k\frac{a}{d}\),这样先把 \(x, y\) 都调整到非负整数,然后再适量缩小就可以了。

时间复杂度 \(O(qn \log n)\),结合以上算法预计得分 \(50pts\)。

Code

#include <iostream>
#include <cstdio> using namespace std; typedef long long LL; const int N = 200005, L = 18;
const LL INF = 9e18; int n, m, dep[N], fa[N][L], A[N << 1], B[N << 1], len1, len2; int ds[N << 1], cnt[N]; int head[N], numE = 0; struct E{
int next, v;
} e[N << 1]; void inline add(int u, int v) {
e[++numE] = (E) { head[u], v };
head[u] = numE;
} void dfs(int u) {
for (int i = 1; i < L && fa[u][i - 1]; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u][0]) continue;
fa[v][0] = u, dep[v] = dep[u] + 1;
dfs(v);
}
} LL exgcd(LL a, LL b, LL &x, LL &y) {
if (b == 0) { x = 1, y = 0; return a; }
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
} LL inline work(LL a, LL b, LL T1, LL T2) {
LL x, y, c = T2 - T1, D1 = a; LL d = exgcd(a, b, x, y);
if (c % d) return INF;
x *= c / d, y *= -c / d;
a /= d, b /= d;
if (x < 0 || y < 0) {
LL k = max((- x - 1) / b + 1, (- y - 1) / a + 1);
x += k * b, y += k * a;
}
LL k = min(x / b, y / a); x -= k * b;
return x * D1 + T1;
} int inline lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = L - 1; ~i; i--)
if (dep[x] - (1 << i) >= dep[y]) x = fa[x][i];
if (x == y) return x;
for (int i = L - 1; ~i; i--)
if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
} int inline d(int x, int y) {
return dep[x] + dep[y] - 2 * dep[lca(x, y)];
} void inline work(int x, int y, int a[], int &len) {
int c1 = 1; len = 1;
if (x == y) { len = 1, a[0] = x; return; }
a[0] = x, ds[0] = y;
while (x != y) {
if (dep[x] > dep[y]) {
x = fa[x][0];
if (x != y) a[len++] = x;
} else {
y = fa[y][0];
if (x != y) ds[c1++] = y;
}
}
for (int i = 0; i < c1; i++) a[len + i] = ds[c1 - i - 1];
len = len + c1;
} LL inline query(int u, int v, int x, int y) {
LL res = INF;
LL D1 = max(2 * d(u, v), 1), D2 = max(2 * d(x, y), 1);
for (int i = 0; i < len1; i++) {
int z = A[i];
if (!cnt[z]) continue;
LL T1 = d(u, z), T2 = d(u, v) + d(v, z), T3 = d(x, z), T4 = d(x, y) + d(y, z);
res = min(res, min(min(work(D1, D2, T1, T3), work(D1, D2, T1, T4)), min(work(D1, D2, T2, T3), work(D1, D2, T2, T4))));
}
return res == INF ? -1 : res;
} int main() {
scanf("%d", &n);
for (int i = 1, u, v; i < n; i++)
scanf("%d%d", &u, &v), add(u, v), add(v, u);
dfs(1);
scanf("%d", &m);
while (m--) {
int u, v, x, y; scanf("%d%d%d%d", &u, &v, &x, &y);
work(u, v, A, len1); work(x, y, B, len2);
for (int i = 0; i < len2; i++) cnt[B[i]]++;
printf("%lld\n", query(u, v, x, y));
for (int i = 0; i < len2; i++) cnt[B[i]]--;
}
return 0;
}

Subtask #5

对于一条链而言,我们发现,两条路径的所有公共点是连续的一段。

设 \(u < v, x < y\),那么这一段左端点 \(c_1 = \max(u, x)\) 右端点 $ c_2 = \min(v, y)$,如果 \(c_1 > c_2\) 即无解。

若像 Subtask 4 一样一个个点考虑发现时间复杂度太大,所以我们即考虑着整个一段。

设 \(X_1\) 为 \(x\) 第一次走到 \(c_1\) 的时间。

设 \(X_2\) 为 \(x\) 第一次走到 \(c_2\) 的时间。

设 \(U_1\) 为 \(u\) 第一次走到 \(c_1\) 的时间。

设 \(U_2\) 为 \(u\) 第一次走到 \(c_2\) 的时间。

这四个量的求法已经在 Subtask 4 讨论过。

我们发现相遇无非两种情况:

  1. 两个教师从同一侧出发,相遇。
  2. 两个教师从两侧相向而行,相遇。

我们只需要两者取最优。

第一种情况

对于第一种情况,显然即向右走同时到 \(c_1\),或向左走同时到 \(c_2\) 两种情况(若相遇到中间,那么上一个时刻肯定也是相同的点,与最小时间矛盾),这里用 Subtask 4 的方法就行了。

第二种情况

对于第二种情况,我们枚举一个老师第一次到一侧端点的时间 \(T_1\),另一个老师第一次到另一侧端点的时间 \(T_2\),即 \(T_1 = U_1, T_2 = X_2\) 或 \(T_1 = U_2, T_2 = X_1\) 两种情况。

由 Subtask 3,我们知道经过这个点的所有时间是 \(xD_1 + T_1\),所以这个教师在 \((c_1, c_2)\) 这段上的时间就是一段区间: \([xD_1 + T_1, xD_1 + T_1 + d_{c_1,c_2})]\).

对于另一个教师,类似。

所以我们就要找到最小的 \(T\),满足:

  1. 相遇在点上而不是边上
  2. 两个区间有交集

第一个条件

设答案为 \(T\),假设相遇在 \(z\) 点,有:

\[T = xD_1 + T_1 + d(c_1, z)
\]
\[T = yD_2 + T_2 + d(z, c_2)
\]

两式相加再除以二:

\[T = \frac{xD_1 + yD_2+T_1+T_2+d_{c_1,c_2}}{2}
\]

为了让 \(T\) 是整数,所以 \(xD_1 + yD_2+T_1+T_2+d_{c_1,c_2}\) 得是偶数,这个表达式前两项显然是偶数(若 \(D_1\) 或 \(D_2\) 为 \(1\),意味着一个人是原地不动的,这种情况在第一种情况已经判过了,所以不影响,当然你也可以用 Subtask 2 的方法),所以只需要检查 \(T_1+T_2+d_{c_1,c_2}\) 是否是偶数就可以了,不是直接直接无解。

第二个条件

\[\max(xD_1 + T_1, yD_2 + T_2) \le T \le \min(xD_1 + T_1 + d_{c_1,c_2}, yD_2 + T_2 +d_{c_1,c_2})
\]

这个满足等价于左右两边任意取出移项都满足不等关系。

拿出来列完后得到不等式等价于:

\[yD_2 + T_2 - T_1 - d_{c_1,c_2} \le xD_1 \le yD_2 + T_2 - T_1 + d_{c_1,c_2}
\]

设 \(P = D_2, L = T_2 - T_1 - d_{c_1,c_2}, R = T_2 - T_1 + d_{c_1,c_2}, D = D_1\),这些数都是常数。

我们要找到 \(yP + L \le xD \le yP + R\),满足最小非负整数解 \(x\),

这样为什么是对的呢,\(y\) 不也要最小吗?

把上面那个不等式等价对称一下:

\[xD_1 + T_1 - T_2 - d_{c_1,c_2} \le yD_2 \le xD_1 + T_1 - T_2 + d_{c_1,c_2}
\]

所以当你 \(x\) 小,\(y\) 所在的区间也小,所以 \(x\) 肯定是得满足能找到 \(y\) 的情况下尽量小。

然后我们证明找到 \(xD_1+T_1-T_2+d_{c_1,c_2} \ge 0\) 且满足上面等式的最小 \(x\) 后, \(y = \lfloor \frac{xD_1+T_1-T_2+d_{c_1,c_2}}{D_2} \rfloor\) ,即 \(y\) 是满足条件里最大的 \(y\)。

看上面那个不等式,显然 \(x\) 变小,\(y\) 也变小,且 \(R - L = 2d_{c_1,c_2} \le P\)。

  • 若 \(R - L = 2d_{c_1,c_2} = P\),这样子路径就是第二个教师的路径,考虑相向而行即 \(u = y, v = x\) 的情况,特判一下,此时让 \(x = 0\) 就可以满足(中间一定存在一个 \(P = D_2\) 的倍数)。
  • 否则就是 \(R - L < P\),这样的话这个区间内最多就只有一个满足条件的 \(y\),所以那么算肯定是对的。

然后我们回归主题算最小的 \(x\)。

首先两个变量 \(P, D\) 你枚举复杂度肯定是不行的。

然后就想到转化为模意义下的不等式。

先把 \(L, D, R\) 都 \(\mod P\)。

特判 \(L > R\) 或 \(L = 0\)(中间一定存在一个 \(P = D_2\) 的倍数),那么让 \(x = 0\),就可以满足这个式子。

特判后的 \(1 \le L \le R < P\),由于 \(R - L < P\),所以他们本身也是同一段下的,所以就是让这个式子在模意义下找到最小的也在那个区间里。

求解奇怪的东西.jpg

设 \(G(L, R, D, P)\) 为 \(yP + L \le xD \le yP + R\),满足 \(1 \le L \le R < P, D < P\),其中 \(x\) 的最小非负整数解。

这是一个模板题,题号是 POJ 3530。

  • 首先若 \(D = 0\) 那么显然就无解。。
  • 否则假设 \(P = 0\),这时候有解,就直接输出(此时肯定是最小值,若 \(P > 0\),那么 \(x\) 的区间会变大)
  • 否则 \(P > 0\),由于上面找不到解(\(L, R\) 中间没有 \(D\) 的倍数),一定有 \(mD < L \le R < (m+1)D\),这样我们就成功的把值域压到的 \(D\) 长度!移项有 \(xD - R \le yP \le xD -L\),所以此时问题转化为了 \(G(-R \mod D, -L \mod D, P \mod D, D)\)

复杂度分析可以关注最后两项,这跟 gcd 的复杂度是一样的,每次迭代 \(D\) 会变为原来的一半。

然后这个东西就是神奇的做到了 \(O(\log n)\),神奇到无与伦比。


时间复杂度 \(O(n \log n)\) 结合上述算法共获得 \(70pts\)。

Code

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std; typedef long long LL; const int N = 200005, L = 18;
const LL INF = 9e18; int n, q; void inline read(int &x) {
x = 0; char s = getchar();
while (s > '9' || s < '0') s = getchar();
while (s <= '9' && s >= '0') x = (x << 1) + (x << 3) + s - '0', s = getchar();
} LL exgcd(LL a, LL b, LL &x, LL &y) {
if (b == 0) { x = 1, y = 0; return a; }
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
} LL inline work1(LL a, LL b, LL T1, LL T2) {
LL x, y, c = T2 - T1, D1 = a; LL d = exgcd(a, b, x, y);
if (c % d) return INF;
x *= c / d, y *= -c / d;
a /= d, b /= d;
if (x < 0 || y < 0) {
LL k = max((- x - 1) / b + 1, (- y - 1) / a + 1);
x += k * b, y += k * a;
}
LL k = min(x / b, y / a); x -= k * b;
return x * D1 + T1;
} LL G(LL L, LL R, LL D, LL P) {
if (!D) return INF;
if (R / D * D >= L) return (L + D - 1) / D;
LL x = G(((-R) % D + D) % D, ((-L) % D + D) % D, P % D, D);
if (x == INF) return INF;
return (x * P + L + D - 1) / D;
} LL inline work2(LL D1, LL D2, LL T1, LL T2, LL D) {
if (D1 == 1 || D2 == 1) return INF;
if ((D + T1 + T2) & 1) return INF;
LL L = ((T2 - T1 - D) % D2 + D2) % D2, R = ((T2 - T1 + D) % D2 + D2) % D2;
LL x1 = 0;
if (L && L <= R && 2 * D < D2) x1 = G(L, R, D1 % D2, D2);
if (x1 == INF) return INF;
LL x2 = (x1 * D1 + T1 - T2 + D) / D2;
if (x1 * D1 + T1 - T2 - D >= 0) x2 = min(x2, (x1 * D1 + T1 - T2 - D + D2 - 1) / D2);
return (x1 * D1 + x2 * D2 + T1 + T2 + D) / 2;
} int inline d(int x, int y) {
return abs(x - y);
} void inline getChain(int u, int v, int x, int y, int &p1, int &p2) {
if (u > v) swap(u, v);
if (x > y) swap(x, y);
p1 = max(u, x), p2 = min(v, y);
} LL inline query(int u, int v, int x, int y) {
int p1, p2;
getChain(u, v, x, y, p1, p2);
if (p1 > p2) return -1;
// p1 - p2 是子路径
int D1 = d(u, v) * 2, D2 = d(x, y) * 2, D = d(p1, p2);
int U1 = d(u, p1), U2 = d(u, p2);
if (U1 < U2) U2 = D1 - U2;
else U1 = D1 - U1;
int X1 = d(x, p1), X2 = d(x, p2);
if (X1 < X2) X2 = D2 - X2;
else X1 = D2 - X1;
if (D1 == 0) D1 = 1;
if (D2 == 0) D2 = 1;
// U1:表示从 U1 出发,第一次踏上 p1 - p2 这条路径,并且起点是 p1 的时间。
LL res = min(work1(D1, D2, U1, X1), work1(D1, D2, U2, X2));
res = min(res, min(work2(D1, D2, U1, X2, D), work2(D1, D2, U2, X1, D)));
return res == INF ? -1 : res;
} int main() {
read(n);
for (int i = 1, u, v; i < n; i++) read(u), read(v);
read(q);
while (q--) {
int u, v, x, y; read(u), read(v), read(x), read(y);
printf("%lld\n", query(u, v, x, y));
}
return 0;
}

Subtask #6

我们发现,在一棵普通树下,两个树上路径的交也应该是一段连续的链(假设有两段,那么有环了)。

因此我们只需要快速算出两个树上路径的子路径,这样我们就可以按照 Subtask 5 做了。

你应该可以大型分类讨论,因为显然端点必须在其中两个点的 LCA 上。

还有一种很方便的找树上路径交的黑科技。

\(dep_x\) 表示 \(x\) 的深度

从 \(lca(u, x), lca(u, y), lca(v, x),lca(v, y)\) 四个点找深度最大的两个点,记为 \(p_1, p_2\)。

  • 若 \(p_1 = p_2\) 且 \(dep_{p1} < \max(dep_{lca(x, y)}, dep_{lca(u, v)})\) 那么相交路径
  • 否则相交路径就是 \(p_1\) 到 \(p_2\)

关于正确性,可以分类讨论每一种情况然后神奇的发现都满足......

时间复杂度 \(O((n + q) \log n)\) 。预计得分 \(100pts\)。

Code

#include <iostream>
#include <cstdio> using namespace std; typedef long long LL; const int N = 200005, L = 18;
const LL INF = 9e18; int n, q, dep[N], fa[N][L]; int head[N], numE = 0; char buf[1<<23], *p1=buf, *p2=buf, obuf[1<<23], *O=obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++) struct E{
int next, v;
} e[N << 1]; void inline read(int &x) {
x = 0; char s = getchar();
while (s > '9' || s < '0') s = getchar();
while (s <= '9' && s >= '0') x = (x << 1) + (x << 3) + s - '0', s = getchar();
} void inline add(int u, int v) {
e[++numE] = (E) { head[u], v };
head[u] = numE;
} void dfs(int u) {
for (int i = 1; i < L && fa[u][i - 1]; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].v;
if (v == fa[u][0]) continue;
fa[v][0] = u, dep[v] = dep[u] + 1;
dfs(v);
}
} int inline lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = L - 1; ~i; i--)
if (dep[x] - (1 << i) >= dep[y]) x = fa[x][i];
if (x == y) return x;
for (int i = L - 1; ~i; i--)
if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
} int inline d(int x, int y, int p) {
return dep[x] + dep[y] - 2 * dep[p];
} LL exgcd(LL a, LL b, LL &x, LL &y) {
if (b == 0) { x = 1, y = 0; return a; }
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
} LL inline work1(LL a, LL b, LL T1, LL T2) {
LL x, y, c = T2 - T1, D1 = a; LL d = exgcd(a, b, x, y);
if (c % d) return INF;
x *= c / d, y *= -c / d;
a /= d, b /= d;
if (x < 0 || y < 0) {
LL k = max((- x - 1) / b + 1, (- y - 1) / a + 1);
x += k * b, y += k * a;
}
LL k = min(x / b, y / a); x -= k * b;
return x * D1 + T1;
} LL G(LL L, LL R, LL D, LL P) {
if (!D) return INF;
if (R / D * D >= L) return (L + D - 1) / D;
LL x = G(((-R) % D + D) % D, ((-L) % D + D) % D, P % D, D);
if (x == INF) return INF;
return (x * P + L + D - 1) / D;
} LL inline work2(LL D1, LL D2, LL T1, LL T2, LL D) {
if (D1 == 1 || D2 == 1) return INF;
if ((D + T1 + T2) & 1) return INF;
LL L = ((T2 - T1 - D) % D2 + D2) % D2, R = ((T2 - T1 + D) % D2 + D2) % D2;
LL x1 = 0;
if (L && L <= R && 2 * D < D2) x1 = G(L, R, D1 % D2, D2);
if (x1 == INF) return INF;
LL x2 = (x1 * D1 + T1 - T2 + D) / D2;
if (x1 * D1 + T1 - T2 - D >= 0) x2 = min(x2, (x1 * D1 + T1 - T2 - D + D2 - 1) / D2);
return (x1 * D1 + x2 * D2 + T1 + T2 + D) / 2;
} LL inline query(int u, int v, int x, int y) {
int p[4] = { lca(u, x), lca(u, y), lca(v, x), lca(v, y)};
int w = lca(u, v), z = lca(x, y);
int p1 = 0, p2 = 0;
for (int i = 0; i < 4; i++)
if (dep[p[i]] > dep[p1]) p2 = p1, p1 = p[i];
else if (dep[p[i]] > dep[p2]) p2 = p[i];
if (p1 == p2 && (dep[p1] < dep[w] || dep[p1] < dep[z])) return -1;
// p1 - p2 是子路径
int D1 = d(u, v, w) * 2, D2 = d(x, y, z) * 2, D = d(p1, p2, lca(p1, p2));
int U1 = d(u, p1, lca(u, p1)), U2 = d(u, p2, lca(u, p2));
if (U1 < U2) U2 = D1 - U2;
else U1 = D1 - U1;
int X1 = d(x, p1, lca(x, p1)), X2 = d(x, p2, lca(x, p2));
if (X1 < X2) X2 = D2 - X2;
else X1 = D2 - X1;
if (D1 == 0) D1 = 1;
if (D2 == 0) D2 = 1;
// U1:表示从 U1 出发,第一次踏上 p1 - p2 这条路径,并且起点是 p1 的时间。
LL res = min(work1(D1, D2, U1, X1), work1(D1, D2, U2, X2));
res = min(res, min(work2(D1, D2, U1, X2, D), work2(D1, D2, U2, X1, D)));
return res == INF ? -1 : res;
} int main() {
read(n);
for (int i = 1, u, v; i < n; i++)
read(u), read(v), add(u, v), add(v, u);
dep[1] = 1, dfs(1);
read(q);
while (q--) {
int u, v, x, y; read(u), read(v), read(x), read(y);
printf("%lld\n", query(u, v, x, y));
}
return 0;
}

尾声

个人感觉这是一道非常好的题,表面上是图论,是指是数学循环节、模意义下的最优化问题,而且有 3 个难点,即求树上两条路径的子路径,\(ax - by = c\) 中 \(x, y\) 都为非负整数的最小整数解,以及最难的 \(L \le Dx \le R \pmod P\) 问题的最小整数 \(x\),还有一堆毒瘤的讨论,我整个人想了数天。。。

—— by MoRanSky, 2020.9.23

CF500G / T148321 走廊巡逻的更多相关文章

  1. 【BZOJ-1912】patrol巡逻 树的直径 + DFS(树形DP)

    1912: [Apio2010]patrol 巡逻 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 1034  Solved: 562[Submit][St ...

  2. BZOJ1912 [Apio2010]patrol 巡逻

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...

  3. BFS 巡逻机器人

    巡逻机器人 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=83498#problem/F 题目大意: 机器人在一个矩形区域巡逻, ...

  4. SCAU巡逻的士兵

    1142 巡逻的士兵 Description 有N个士兵站成一队列, 现在需要选择几个士兵派去侦察. 为了选择合适的士兵, 多次进行如下操作: 如果队列超过三个士兵, 那么去除掉所有站立位置为奇数的士 ...

  5. 【BZOJ】【1912】【APIO2010】patrol巡逻

    树形DP 说是树形DP,其实就是求树的最长链嘛…… K=1的时候明显是将树的最长链的两端连起来最优. 但是K=2的时候怎么搞? 考虑第一次找完树的最长链以后的影响:第一次找过的边如果第二次再走,对答案 ...

  6. bzoj 1912 巡逻(树直径)

    Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Ou ...

  7. 巡逻机器人(BFS)

    巡逻机器人问题(F - BFS,推荐) Description   A robot has to patrol around a rectangular area which is in a form ...

  8. [Apio2010] 巡逻

    Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Ou ...

  9. CH6201 走廊泼水节【最小生成树】

    6201 走廊泼水节 0x60「图论」例题 描述 [简化版题意]给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树.求增加的边的权值总和最小是多少. 我 ...

随机推荐

  1. 异常记录-Gradle依赖掉坑之旅

    前言 最近在项目中遇到了一个问题,死活拉不下来依赖,耗费了一整天,感觉自己真是菜的抠脚. 没想到今天脑子一清醒,刷刷的问题逐个击破了. 问题描述: 项目成员添加了新的依赖,然后我这边项目拉下来,bui ...

  2. Linux中Python自动输入sudo 密码

    一.背景和需求 背景: 由于docker服务进程都是以root帐号的身份运行的,所以用docker跑abpred出来的文件所有者都是root, 而我作为一般用户,操作这个文件不够权限,运行代码时需要s ...

  3. Matlab项目经验分享-去除震荡点

    Matlab是做科研是比较常用的建模工具,我在研一做项目期间遇到了一个还算比较基础的问题,所以我打算记录下来并分享出来! 处理问题步骤: 1. 抛出问题 2. 思考解决方法 3. 代码验证看结果 抛出 ...

  4. centos8 mysql8遇到的问题

    1.装了第一遍,连接没遇到问题,没注意是怎么装的:本机连,外部连都没碰到问题: 遇到了表名大小写的问题,改了配置文件my.cnf或/etc/my.cnf.d/mysql-server.cnf的文件 在 ...

  5. 记php多张图片 合并生成竖列 纵向长图(可用于商品详情图合并下载)

    <?php namespace app\mapi\common\image; /** * 拼接多幅图片成为一张图片 * * 参数说明:原图片为文件路径数组,目的图片如果留空,则不保存结果 * * ...

  6. ABBYY FineReader 14新增了什么

    FineReader 是一款一体化的 OCR 和PDF编辑转换器,随着版本的更新,功能的增加,FineReader 14的推出继续为用户在处理文档时提高业务生产力,该版本包含若干新特性和功能增强,包括 ...

  7. Mac升级资料丢失怎么办?EasyRecovery能恢复嘛?

    随着越来越多的用户选择性能更高的mac笔记本来工作,一般情况下,为了保证用户有一个很好的使用体验,Mac系统会在一定的时间内进行系统的更新,弥补前一个版本的不足.结果就有一些用户反应Mac升级后,电脑 ...

  8. JDBC事务提交机制以及解决方案

    JDBC中的事务是自动提交的,什么是自动提交? 只要任意执行一条DML语句,则自动提交一次.这是JDBC默认的事务行为.但是实际业务当中,通常都是N条DML语句共同联合才能完成的,必须保证它们这些DM ...

  9. iOS中如何使定时器NSTimer不受UIScrollView滑动所影响

    以下是使用 scheduledTimerWithTimeInterval 方法来实现定时器 - (void)addTimer { NSTimer scheduledTimerWithTimeInter ...

  10. iPhone/iOS开启个人热点的相关位置调整小结

    冬至已到,圣诞将近,最近公司项目实在太多,三四个项目反复的切换真的让人焦头烂额,趁今天有点空,把维护的三个项目顺利送出,刚好可以缕缕思路,记录一下最近遇到的问题.说不着急那是假的,客户一天天的催的确实 ...