• CF500G / T148321 走廊巡逻


    题目链接

    这题是 Codeforces Goodbye 2014 的最后一题 CF500G,只是去掉了 (u ot= x, v ot = 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
    ", 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
    ", 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
    ", 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 + kfrac{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
    ", 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} floor) ,即 (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
    ", 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
    ", 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

  • 相关阅读:
    sourceInsight4 破解笔记(完美破解)
    notepad++ 查找引用(Find Reference)(适用于c c++及各类脚本比如lua、python等)
    notepad++ 插件推荐——快速定位文件
    WebRTC开源项目一览之二
    编译最新版webrtc源码和编译好的整个项目10多个G【分享】
    Neo4j中实现自定义中文全文索引
    NEO4J -模糊查询
    neo4j数据库迁移---------Neo4j数据库导入导出的方法
    使用neo4j图数据库的import工具导入数据 -方法和注意事项
    neo4j采坑记
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13734759.html
Copyright © 2020-2023  润新知