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 = \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\) 的最小时间,最后取最小值即可。

那么怎么算呢?

先考虑一个老师。

img

在一次循环节中,有两个时刻 \(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

posted @ 2020-09-26 13:26  DMoRanSky  阅读(237)  评论(0编辑  收藏  举报