题目链接

这题是 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. seaborn库中柱状图绘制详解

    柱状图用于反映数值变量的集中趋势,用误差线估计变量的差值统计.理解误差线有助于我们准确的获取柱状图反映的信息,因此打算先介绍一下误差线方面的内容,然后介绍一下利用seaborn库绘制柱状图. 1.误差 ...

  2. linux中ugo权限管理(chmod/chown)

    查看ugo权限: ll  [root@localhost test]# ll total 12 -rwxr-xr-x 2 root root 4 Oct  3 11:44 a lrwxrwxrwx 1 ...

  3. 调用外部接口支持https请求

    1,创建RestTemplateConfig.java文件,内容如下: package com.htsec.monitor.internet.config;import com.htsec.monit ...

  4. JS控制Video播放器(快进、后退、播放、暂停、音量大小)

    思路: 一.首先监听触发事件. 比如:向上键对应的keyCode为38,向下键对应的keyCode为40,向左键对应的keyCode为37,向右键对应的keyCode为39,空格键对应的keyCode ...

  5. NAT基本原理及应用

    参考链接 https://blog.csdn.net/u013597671/article/details/74275852

  6. 应用程序-特定 权限设置并未向在应用程序容器不可用 SID (不可用)中运行的地址 LocalHost (使用 LRPC) 中的用户...的 COM 服务器应用程序的 本地 激活 权限。此安全权限可以使用组件服务管理工具进行修改。

    很久以前发现我们的业务服务器上出现一个System的系统严重错误,查找很久都没有找到解决办法,今日再次查看服务器发现报错更频繁,于是就搜集各种资料进行查找解决办法,终于找到了一个解决办法. 错误截图介 ...

  7. Java8用了这么久了,Stream 流用法及语法你都知道吗?

    1.简介 Stream流 最全的用法Stream 能用来干什么?用来处理集合,通过 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询,Stream API 提供了一 ...

  8. ccpc2020长春站F题 Strange Memory

    dsu on tree 题目链接 点我跳转 题目大意 给定一棵包含 \(n\) 个节点的树,每个节点有个权值 \(a_i\) 求\(∑_{i=1}^n∑_{j=i+1}^n[a_i⊕a_j=a_{lc ...

  9. python应用(1):安装与使用

    程序员的基本工作是写程序,而写程序要用到编程语言,编程语言可以分为编译型语言跟解释型语言. 编译型语言,就是在执行代码之前,先把源代码编译(加链接)成另一种形式的代码,比如目标代码,或字节码,这种代码 ...

  10. H5系列之video自己编写控制栏

    首先来了解一下 video, video呢,是H5 的标签,别人说的 H5播放器,没错 就是他了,利用video标签,可以实现视频播放. 但是啊,你会发现,在不同的浏览器上,播放器的 控制栏,都是不一 ...