[CF1062E] Company 题解
[CF1062E] Company 题解
前置芝士:
- \(\text {LCA}\) 问题
- \(\text{RMQ}\) 问题
- 利用 \(dfn\) 转换树上问题
题目描述
给定一颗树,有若干个询问,每个询问给出 \(l\),\(r\),要求编号为 \(l\)~\(r\) 的点任意删去一个之后剩余点的 \(LCA\) 深度最大,输出删去点的编号和 \(LCA\) 的最大深度。
多点 \(\text{LCA}\)
见微知著,先考虑多个点的 \(\text{LCA}\) 怎么求。
结论:
假设多个点组成的序列为 \(S\),则有
其中 \(vis[i]\) 表示欧拉序中第 \(i\) 个数,\(dfn[i]\) 表示编号为 \(i\) 的节点的欧拉序。
翻译成人话就是 \(S\) 中所有点的 \(\text{LCA}\) 等价于 \(S\) 中 \(dfn\) 序最小的点与 \(dfn\) 序最大的点的 \(\text{LCA}\)。
证明:
设最终求出的 \(\text{LCA}\) 为 \(lca\)
存在性:
即证明 \(S\) 中剩余点都有 \(lca\) 作为祖先。
设 \(lca\) 的子树中 \(dfn\) 最大的节点为 \(r\),显然子树中 \(dfn\) 最小的节点为 \(lca\) 本身,此子树在欧拉序上是一段连续的区间。
\[dfn_{lca} \le dfn_u \le dfn_v\le dfn_r\\ \]\[dfn_{lca} \le dfn_u \le dfn_i \le dfn_v\le dfn_r(i\in S)\\ \]\[dfn_{lca} \le dfn_i \le dfn_r(i\in S)\\ \]因此对于 \(S\) 内所有的元素都在 \(lca\) 的子树内部,\(S\) 中剩余点都有 \(lca\) 作为祖先。
最优性,根据 \(\text{LCA}\) 的定义,显然所有深度大于 \(lca\) 的节点不满足它是 \(u, v\) 的公共祖先。
证毕。
思路
进一步考虑删除。
根据上一标题下的结论可以发现对答案有影响的节点只有 \(u\) 和 \(v\),只需要判断删除 \(u\) 更优还是删除 \(v\) 更优,然后重新求一遍 \(\text{LCA}\) 即可。
更多的细节可以看代码
实现
时间瓶颈在 RMQ 与 LCA 上,RMQ 上我用了线段树才不会告诉你ST表调不出来呢Q^Q,多了一个 \(\log n\),LCA 上我用了 RMQ。
时间复杂度:\(O(n\log n + q\log n)\)。
// Problem: Company
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1062E
// Memory Limit: 250 MB
// Time Limit: 2000 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2023-01-30 11:32:04
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <tuple>
#include <unordered_map>
#define x first
#define y second
#define int long long
#define speedup (ios::sync_with_stdio(0), cin.tie(0), cout.tie(0))
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
vector<int> g[N];
int pos[N], idx, dfn[N << 1], dep[N], vis[N << 1];
int st[21][N], lg[N << 1];
int n, m;
void dfs(int p, int fa)
{
dfn[p] = ++idx;
vis[idx] = p;
pos[p] = idx;
for (auto i : g[p])
{
if (i == fa)
continue;
dep[i] = dep[p] + 1;
dfs(i, p);
vis[++idx] = p;
}
}
int Min(int a, int b)
{
return pos[a] < pos[b] ? a : b;
}
void ST()
{
lg[1] = 0;
for (int i = 2; i <= (N << 1); i++)
lg[i] = lg[i >> 1] + 1;
for (int i = 1; i <= (N << 1); i++)
st[0][i] = vis[i];
for (int i = 1; i <= lg[(N << 1) - 1]; i++)
for (int j = 1; j + (1 << i) <= (N << 1); j++)
st[i][j] = Min(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
}
int LCA(int u, int v)
{
int l = pos[u], r = pos[v];
if (l > r)
swap(l, r);
int k = lg[r - l + 1];
return Min(st[k][l], st[k][r - (1 << k) + 1]);
}
struct owo
{
int l, r, max, min = INF;
};
struct SegmentTree
{
owo t[N << 2];
inline void up(owo &f, owo &ls, owo &rs)
{
f.max = max(ls.max, rs.max);
f.min = min(ls.min, rs.min);
}
inline void up(int k)
{
up(t[k], t[k << 1], t[k << 1 | 1]);
}
void build(int k, int l, int r)
{
t[k].l = l, t[k].r = r;
if (l == r)
t[k] = {l, r, dfn[l], dfn[l]};
else
{
int mid = l + r >> 1;
build(k << 1, l, mid), build(k << 1 | 1, mid + 1, r);
up(k);
}
}
owo query(int k, int ll, int rr)
{
int l = t[k].l, r = t[k].r, mid = l + r >> 1;
if (ll <= l && rr >= r)
return t[k];
if (ll > mid)
return query(k << 1 | 1, ll, rr);
else if (rr <= mid)
return query(k << 1, ll, rr);
auto L = query(k << 1, ll, rr), R = query(k << 1 | 1, ll, rr);
owo tmp = {0, 0, 0, 0};
up(tmp, L, R);
return tmp;
}
} tr;
signed main()
{
speedup;
cin >> n >> m;
for (int i = 2; i <= n; i++)
{
int tmp;
cin >> tmp;
g[tmp].push_back(i);
}
dep[1] = 0;
dfs(1, 0), ST();
// cout << dfn[3] << endl;
tr.build(1, 1, N);
for (int i = 1; i <= m; i++)
{
int l, r;
cin >> l >> r;
auto tmp = tr.query(1, l, r);
int u = tmp.min, v = tmp.max;
u = vis[u], v = vis[v];
// 找到删除 u/v 后的区间最小值,重新求一遍 LCA
int uu = min((l <= u - 1 ? tr.query(1, l, u - 1).min : INF), (r >= u + 1 ? tr.query(1, u + 1, r).min : INF));
int vv = max((l <= v - 1 ? tr.query(1, l, v - 1).max : -INF), (r >= v +1 ? tr.query(1, v + 1, r).max : -INF));
uu = vis[uu], vv = vis[vv];
int d1 = LCA(uu, v), d2 = LCA(vv, u);
// d1 => 删u,d2 => 删v
if (dep[d1] >= dep[d2])
cout << u << ' ' << dep[d1] << "\n";
else
cout << v << ' ' << dep[d2] << "\n";
}
return 0;
}