[CF1062E] Company 题解

[CF1062E] Company 题解

前置芝士:

  • \(\text {LCA}\) 问题
  • \(\text{RMQ}\) 问题
  • 利用 \(dfn\) 转换树上问题

题目描述

给定一颗树,有若干个询问,每个询问给出 \(l\)\(r\),要求编号为 \(l\)~\(r\) 的点任意删去一个之后剩余点的 \(LCA\) 深度最大,输出删去点的编号和 \(LCA\) 的最大深度。

多点 \(\text{LCA}\)

见微知著,先考虑多个点的 \(\text{LCA}\) 怎么求。

结论:

假设多个点组成的序列为 \(S\),则有

\[u = vis[\min_{i \in S}(dfn[i])],\\ v = vis[\max_{i \in S}(dfn[i])]\\ \text{LCA}(S_i) = \text{LCA} (u, v) \]

其中 \(vis[i]\) 表示欧拉序中第 \(i\) 个数,\(dfn[i]\) 表示编号为 \(i\) 的节点的欧拉序。

翻译成人话就是 \(S\) 中所有点的 \(\text{LCA}\) 等价于 \(S\)\(dfn\) 序最小的点与 \(dfn\) 序最大的点的 \(\text{LCA}\)

证明:

设最终求出的 \(\text{LCA}\)\(lca\)

  1. 存在性:

    即证明 \(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\) 作为祖先。

  2. 最优性,根据 \(\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;
}
posted @ 2023-01-30 17:24  MoyouSayuki  阅读(32)  评论(0编辑  收藏  举报
:name :name