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个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树.求增加的边的权值总和最小是多少. 我 ...
随机推荐
- PF_PACKET抓包mmap
PACKET套接口创建 内核函数packet_create处理PF_PACKET套接口的创建工作.其参数sock->type决定了采用哪一种工作模式,如果参数type为SOCK_PACKET即第 ...
- redis乐观锁
乐观锁(又名乐观并发控制,Optimistic Concurrency Control,缩写"OCC"),是一种并发控制的方法.它假设多用户并发的事务在处理时不会彼此互相影响,各事 ...
- 【JVM】肝了一周,吐血整理出这份超硬核的JVM笔记(升级版)!!
写在前面 最近,一直有小伙伴让我整理下关于JVM的知识,经过十几天的收集与整理,初版算是整理出来了.希望对大家有所帮助. JDK 是什么? JDK 是用于支持 Java 程序开发的最小环境. Java ...
- php(tp5)生成条形码
因为公司业务需要,研究了一下条形码 1.下载barcodegen扩展包 官网地址:https://www.barcodebakery.com 2.下载完后解压至 extend 文件夹里面,然后复制以下 ...
- FL Studio12如何进行图示编辑
FL Studio在国内被大家 亲切的称为"水果"深受喜爱玩电音的音乐人的追捧,本章节采用图文结合的方式给大家讲解它的FL Studio12是如何进行图示编辑的. 单击图示按钮可以 ...
- pytest的setup和teardown
学过unittest的setup和teardown,前置和后置执行功能.pytest也有此功能并且功能更强大,今天就来学习一下吧. 用例运行级别: 模块级(setup_module/teardown_ ...
- nmap安装和使用
nmap安装和使用 安装 官网地址 https://nmap.org/download.html 许多流行的Linux发行版(Redhat.Mandrake.Suse等)都使用RPM软件包管理系统,方 ...
- Matlab 数值计算
本博客记录一些简单的计算 det(A):矩阵求行列式 A=[1,2;3,4]; det(A) ans=-2; inv(A):矩阵求逆 A=[1,2;3,4]; B=inv(A) B=[-2,1;1,5 ...
- Pytest学习(十二)-生成HTML报告插件之pytest-html的使用
环境前提 Python3.6+ 安装插件 pip3 install pytest-html -i http://pypi.douban.com/simple/ --trusted-host pypi. ...
- mysql 数据文件
mysql8.0取消了frm文件 . ibd数据和索引