题解 CF500G 【New Year Running】
为了混积分把很久以前的题解搞上来
之前的题解是为了 T148321 这个搬完加了部分分的题写的,在 这(部分分想法也可以在这里看),这里如果有逻辑递推混乱的可能是由于我拼起来的缘故。
这题的总体思路:即找两个循环序列的最小位置,使得他们的值相同。把公共的部分挖出来,发现对于每个循环序列都是连续的一段,然后通过树上的一些性质转化为 Exgcd 与 同余不等式解决。
循环节长度
题目也有明确的提示,一个人按时刻顺序经过的编号是一个循环节,设
所以,循环节长度是和
- 一个暴力做法是把循环节序列找出来(可以暴力 dfs / 一个个跳 LCA,找都是
O(n) 的)然后,枚举答案最多只需要到g 时刻,检查一下当前时刻走到的点是否相同就行了。时间复杂度O(tn^2) 。
一个部分分的想法
既然是一个循环节,我们尝试枚举两个路径都经过的点
那么怎么算呢?
先考虑一个人。
在一次循环节中,有两个时刻
考虑
对于另一个人同理,对于每一个人,我们找一个这样的数量关系
这样联立
问题即变成了求
用 exgcd 就可以了,先把
这样就有一组
时间复杂度
树上路径交
我们发现,两个树上路径的交(公共路径)也应该是一段连续的一条链(假设有两段,那么有环了)。
因此我们需要快速算出两个树上路径的子路径的两个端点。
你应该可以大型分类讨论,因为显然端点必须在其中两个点的 LCA 上。
还有一种很方便的找树上路径交的黑科技。
关于正确性,可以分类讨论每一种情况然后神奇的发现都满足......
分类讨论在公共路径相交的情况
设
若一样一个个点考虑发现时间复杂度太大,所以我们即考虑着整个一段。
设
设
设
设
这些都是 LCA 简单算一下距离就好了。
我们发现相遇无非两种情况:
- 两个人从同一侧出发,相遇。
- 两个人从两侧相向而行,相遇。
我们只需要两者取最优。
第一种情况
对于第一种情况,显然即向右走同时到
第二种情况
对于第二种情况,我们枚举一个人第一次到一侧端点的时间
第一个人经过
对于另一个人,类似。
所以我们就要找到最小的
- 相遇在点上而不是边上
- 两个区间有交集
第一个条件
设答案为
两式相加再除以二:
为了让
第二个条件
这个满足等价于左右两边任意取出移项都满足不等关系。
拿出来列完后得到不等式等价于:
设
我们要找到
这样为什么是对的呢,
把上面那个不等式等价对称一下:
因为当你
看上面那个不等式,显然
- 若
R - L = 2d_{c_1,c_2} = P ,这样子路径就是第二个人的路径下,此时让x = 0 就可以满足(中间一定存在一个P = D_2 的倍数)。 - 否则就是
R - L < P ,这样的话这个区间内最多就只有一个满足条件的y ,所以那么算肯定是对的。
然后我们回归主题算最小的
首先两个变量
然后就想到转化为模意义下的不等式。
先把
特判
特判后的
定义
求解同余不等式.jpg
设
这是一个模板题,题号是 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) 。这里可以模的基础也是,L, R 的区间限制压到了同一段,而P 单点的模是没有影响的。
关于不等式两边可以取模的问题,国内没有较好的解释,基准点,同一段的想法也是我自己想出来的,若有理解不清 & 疏漏情况请评论)
复杂度分析可以关注最后两项,这跟 gcd 的复杂度是一样的,每次迭代
然后这个东西就是神奇的做到了
时间复杂度
#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 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 || L > R) 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 gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
LL work2(LL D1, LL D2, LL T1, LL T2, LL D) {
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 && 2 * D < D2 && L <= R) x1 = G(L, R, D1 % D2, D2);
if (x1 == INF) return INF;
LL x2 = (x1 * D1 + T1 - T2 + D) / 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) * 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;
// 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() {
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;
}