P3533 [POI2012] RAN-Rendezvous 题解
Laoshan_PLUS · · 题解
基环树典型题。
题意
给定一个
- 从
a 出发走x 步和从b 出发走y 步会到达同一个点。 - 在 1 的基础上如果有多解,那么要求
\max(x,y) 最小。 - 在 1 和 2 的基础上如果有多解,那么要求
\min(x,y) 最小。 - 如果在 1、2、3 的基础上仍有多解,那么要求
x\ge y 。
如果不存在满足条件 1 的 -1 -1。
解析
仍然用基环树的惯用套路,分类讨论。
若存在满足条件 1 的
- 若
a,b 都不在一个基环树里,直接输出-1 -1。 - 若
a,b 在基环树环上同一点的子树内,则此时的交汇点就是a,b 的 LCA,不难证明。 - 否则,答案要么是
a 所在子树根节点,要么是b 所在子树根节点,同样不难证明:因为环是单向的,所以只要不是在这两个根节点,都会徒增路程而没有任何效益。
那么有了思路,接下来考虑如何实现。
首先肯定是一个 DFS 判基环树,对每个点作标记,然后就可以特判掉情况 1。
其次,对于情况 2,我们需要求出两点 LCA。肯定是用倍增,所以我们需要预处理
最后,对于情况 3,我们需要求出:
实现
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 5e5 + 5;
int n, q;
vector<int> g[MAXN], G[MAXN];
int tr[MAXN];
bool vis[MAXN];
int stk[MAXN], top;
bool ring[MAXN], ins[MAXN];
int len[MAXN], dis[MAXN], ist[MAXN], tot;
int dep[MAXN], fa[MAXN][21];
void isring(int u) {
if (ins[u]) {
ring[u] = 1;
for (int i = top; stk[i] != u; i--) ring[stk[i]] = 1;
return;
}
if (vis[u]) return;
vis[u] = 1;
stk[++top] = u;
ins[u] = 1;
for (auto v : g[u]) isring(v);
--top;
ins[u] = 0;
}
void getfa(int u, int fno, int uu) {
// cout << u << '\n';
tr[u] = uu;
fa[u][0] = fno;
dep[u] = dep[fno] + 1;
for (int i = 1; i <= 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (auto v : G[u]) if (!ring[v]) getfa(v, u, uu);
}
int lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
for (int i = 20; i >= 0; i--)
if (dep[fa[u][i]] >= dep[v])
u = fa[u][i];
if (u == v) return v;
for (int i = 20; i >= 0; i--)
if (fa[u][i] != fa[v][i]) {
u = fa[u][i];
v = fa[v][i];
}
return fa[u][0];
}
void ringlen(int u, int t, int fno) {
if (dis[u]) return;
ist[u] = t;
len[t]++;
dis[u] = dis[fno] + 1;
for (auto v : g[u]) if (ring[v]) ringlen(v, t, u);
}
bool check(int a1, int a2, int b1, int b2) {
if (max(a1, a2) != max(b1, b2)) return max(a1, a2) < max(b1, b2);
if (min(a1, a2) != min(b1, b2)) return min(a1, a2) < min(b1, b2);
return a1 >= a2;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(nullptr);
// freopen("ran3c.in", "r", stdin);
// freopen("ans.out", "w", stdout);
cin >> n >> q;
for (int i = 1, v; i <= n; i++) {
cin >> v;
// if (i == v) continue;
g[i].push_back(v);
G[v].push_back(i);
}
// for (int i = 1; i <= n; i++) {
// cout << i << ": ";
// for (auto v : G[i]) cout << v << ' ';
// cout << '\n';
// }
for (int i = 1; i <= n; i++) if (!vis[i]) isring(i);
// for (int i = 1; i <= n; i++) cout << ring[i] << '\n';
for (int i = 1; i <= n; i++) if (ring[i]) getfa(i, 0, i);
for (int i = 1; i <= n; i++) if (ring[i] && !dis[i]) ringlen(i, ++tot, 0);
// cout << find(1) << ' ' << find(4) << ' ' << find(5) << '\n';
// cout << len[ist[1]] << '\n';
int x, y;
while (q--) {
cin >> x >> y;
if (ist[tr[x]] != ist[tr[y]]) cout << "-1 -1\n";
else if (tr[x] == tr[y]) {
int LCA = lca(x, y);
cout << dep[x] - dep[LCA] << ' ' << dep[y] - dep[LCA] << '\n';
} else {
int ans1 = dep[y] - 1 + (dis[tr[x]] - dis[tr[y]] + len[ist[tr[x]]]) % len[ist[tr[x]]];
int ans2 = dep[x] - 1 + (dis[tr[y]] - dis[tr[x]] + len[ist[tr[x]]]) % len[ist[tr[x]]];
// cout << dep[x] - 1 << ' ' << ans1 << ' ' << ans2 << ' ' << dep[y] - 1 << ' ';
// cout << check(dep[x] - 1, ans1, ans2, dep[y] - 1) << '\n';
if (check(dep[x] - 1, ans1, ans2, dep[y] - 1)) cout << dep[x] - 1 << ' ' << ans1 << '\n';
else cout << ans2 << ' ' << dep[y] - 1 << '\n';
}
}
return 0;
}