CF500G / T148321 走廊巡逻
这题是 Codeforces Goodbye 2014 的最后一题 CF500G,只是去掉了 \(u \not= x, v \not = v\) 的条件。
官方题解感觉有很多东西说的迷迷瞪瞪,等到自己写的时候就狂 WA 不止。。
前置知识:Exgcd、LCA,没了)
Subtask #1
题目也有明确的提示,一个教师按时刻顺序经过的编号是一个循环节,设 \(D\) 为循环节长度,\(u, v\) 是这条路径,\(d_{u, v}\) 是 \(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)\) 的简单路径上,即满足:
\]
不在就 \(-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 讨论过。
我们发现相遇无非两种情况:
- 两个教师从同一侧出发,相遇。
- 两个教师从两侧相向而行,相遇。
我们只需要两者取最优。
第一种情况
对于第一种情况,显然即向右走同时到 \(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\),满足:
- 相遇在点上而不是边上
- 两个区间有交集
第一个条件
设答案为 \(T\),假设相遇在 \(z\) 点,有:
\]
\]
两式相加再除以二:
\]
为了让 \(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}\) 是否是偶数就可以了,不是直接直接无解。
第二个条件
\]
这个满足等价于左右两边任意取出移项都满足不等关系。
拿出来列完后得到不等式等价于:
\]
设 \(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\) 不也要最小吗?
把上面那个不等式等价对称一下:
\]
所以当你 \(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 走廊巡逻的更多相关文章
- 【BZOJ-1912】patrol巡逻 树的直径 + DFS(树形DP)
1912: [Apio2010]patrol 巡逻 Time Limit: 4 Sec Memory Limit: 64 MBSubmit: 1034 Solved: 562[Submit][St ...
- BZOJ1912 [Apio2010]patrol 巡逻
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...
- BFS 巡逻机器人
巡逻机器人 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=83498#problem/F 题目大意: 机器人在一个矩形区域巡逻, ...
- SCAU巡逻的士兵
1142 巡逻的士兵 Description 有N个士兵站成一队列, 现在需要选择几个士兵派去侦察. 为了选择合适的士兵, 多次进行如下操作: 如果队列超过三个士兵, 那么去除掉所有站立位置为奇数的士 ...
- 【BZOJ】【1912】【APIO2010】patrol巡逻
树形DP 说是树形DP,其实就是求树的最长链嘛…… K=1的时候明显是将树的最长链的两端连起来最优. 但是K=2的时候怎么搞? 考虑第一次找完树的最长链以后的影响:第一次找过的边如果第二次再走,对答案 ...
- bzoj 1912 巡逻(树直径)
Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Ou ...
- 巡逻机器人(BFS)
巡逻机器人问题(F - BFS,推荐) Description A robot has to patrol around a rectangular area which is in a form ...
- [Apio2010] 巡逻
Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Ou ...
- CH6201 走廊泼水节【最小生成树】
6201 走廊泼水节 0x60「图论」例题 描述 [简化版题意]给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树.求增加的边的权值总和最小是多少. 我 ...
随机推荐
- 对“线上问题 不能gdb调试怎么处理??“”的思考
Q1:线上问题的process 都为release版本!不带调试信息怎么查?(目前有时需要查线上问题, 不得不解决这个问题) 之前查问题都是编译环境编译一个带有debug信息的版本进行替换来调试,但是 ...
- java服务器部署开源项目(若依)
1准备工作 (1)阿里云 centos_8_0_x64_20G_alibase_20200218.vhd [root@iZ2zeeqw5fxmm9zagf439aZ ~]# cat /etc/redh ...
- Kubernetes笔记(六):了解控制器 —— Deployment
Pod(容器组)是 Kubernetes 中最小的调度单元,可以通过 yaml 定义文件直接创建一个 Pod.但 Pod 本身并不具备自我恢复(self-healing)功能.如果一个 Pod 所在的 ...
- 解决SSH显示中文乱码的问题(cent os7)
用SSH连接服务器显示中文乱码,试过修改SSH端,不成功.这次从服务器端下手 1.先查看服务器现有的字符集 [root@dm01 ~]# locale -a 在结果中找到 如果没有支持的字符集就需要安 ...
- Python面试题_中级版
Python 面试题 1.Python是如何进行内存管理的 对象引用机制.垃圾回收机制.内存池机制 1.1对象引用机制 Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数. 引 ...
- tp5配置引入使用redis
1.首先你的php得是已经安装了redis扩展的 2.在tp里找到config.php配置文件,找到cache,改成下面的样子 'cache' => [ // 选择模式 'type' => ...
- 「CSP-S 2020」动物园
description luogu loj(暂无数据) solution 这道题作为T2,对选手们考试开始后先通看一遍所有题目的好习惯,以及判断究竟谁才是真正的签到题的重要能力进行了较好的锻炼, 特别 ...
- 如何获取公网IP的mac地址
如何获取远程IP的mac地址 思路分析 由于java本身没有相关的jar包进行获取,所以这里介绍从其他的方面进行入手和实践 使用的工具对比: tcpdump tshark pcap4j 都可以达到抓包 ...
- 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)
(这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...
- idea 安装教程
臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭臭是猪臭 ...