NC22544 车站

发布时间 2023-06-23 16:27:54作者: 空白菌

题目链接

题目

题目描述

一个国家有n个城市,有n-1条道路连接,保证联通。还有m条铁路,从1~m编号,第i条铁路是从ui到vi的简单路径,多次询问一段区间的铁路的车站。

一个点可以作为区间[L,R]铁路的车站满足以下条件:

1、R-L+1条铁路都经过这个车站。

2、R-L+1条铁路经过的所有城市中,离车站最远的城市,与它的距离最小。如果有多个,那么选择编号较小的。

并且存在铁路发生改变的情况。

输入描述

第一行两个整数n,m,表示城市的个数和铁路条数。
接下来n-1行,每行两个整数,描述一条道路。
接下来m行,每行两个整数,描述一条铁路(注意道路与铁路的区分)。
然后一行一个整数Q,表示询问个数。
接下来Q行,每行3个或者4个整数,描述一次操作,具体如下:
操作1:1,l,r,表示询问[l,r]铁路的车站。
操作2:2,x,u,v,表示第x条铁路变成从u到v的一条简单路径。
\(n,m,Q \leq10^5, \ \ u,v\leq n, \ \ \ l,r\leq m\)

输出描述

对于每次询问操作,输出一个整数,表示车站的编号。如果没有满足条件的城市可以作为车站,输出-1。

示例1

输入

6 3
1 2
1 3
2 4
2 5
4 6
4 5
2 3
1 3
7
1 1 1 
1 1 2
1 1 3
2 3 1 6
1 2 3
2 1 1 4
1 1 2

输出

2
2
-1
2
1

题解

知识点:树链剖分,LCA,线段树。

考虑用线段树维护铁路的路线交路径 \((u,v)\),在 \(u\) 外侧距离 \(u\) 最远的车站距离 \(du\) ,在 \(v\) 外侧距离 \(v\) 最远的车站距离 \(dv\)

铁路合并时,我们需要求出路径交,运用到一个黑科技:

  1. 俩俩组合两条路径的端点求LCA,得到 \(4\) 个LCA 。
  2. 取深度最大的两个LCA。
  3. 若相同且深度大于某条路径的LCA,则交路径为空集,否则即为交路径的两个端点。

然后,我们需要求出新的 \(du,dv\) 。显然,新的最远车站一定从旧的两组最远车站得到,因此我们分别求出两条路径原先的最远车站到交路径端点的距离,这个可以直接从最远距离维护。其中,需要注意交路径的方向不一定和原先路径方向一致,交路径端点对应到最远车站更近的一侧。

更新非常容易,直接修改对应节点信息即可。

查询某个区间的铁路时,先求出这组铁路的交路径和最远车站距离,而最优车站一定是总距离的中点,我们分类讨论:

  1. 交路径为空集,输出 \(-1\)
  2. 否则,若中点出现在 \(u,v\) 上及其外侧,那么直接取 \(u,v\) 即可。注意这里中点不能总距离除 \(2\) 得到,因为会出现 \(u,v\) 同时是中点,但 \(u\) 编号更大却选了 \(u\) 的情况。当然也可以在判断前将编号小的换到 \(u\) 上。
  3. 否则,中点在 \((u,v)\) 内,我们需要找到这个中点,取 \(mid\) 为总距离除以 \(2\) 取下整,按总距离奇偶讨论:
    1. 总距离为奇数,则有唯一确定的中点,直接找 \((u,v)\) 路径上距离 \(u\)\(mid - du\) 的点。
    2. 否则,有两个中点, \((u,v)\) 路径上距离 \(u\)\(mid - du\) 的点和距离 \(u\)\(mid+1 - du\) 的点,取编号最小的那个。

时间复杂度 \(O(n + m \log m + q \log m \log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct HLD {
    vector<int> siz, dep, fat, son, top, dfn, L, R;

    HLD() {}
    HLD(int rt, const vector<vector<int>> &g) { init(rt, g); }

    void init(int rt, const vector<vector<int>> &g) {
        assert(g.size() >= 2);
        int n = g.size() - 1;
        siz.assign(n + 1, 0);
        dep.assign(n + 1, 0);
        fat.assign(n + 1, 0);
        son.assign(n + 1, 0);
        top.assign(n + 1, 0);
        dfn.assign(n + 1, 0);
        L.assign(n + 1, 0);
        R.assign(n + 1, 0);

        function<void(int, int)> dfsA = [&](int u, int fa) {
            siz[u] = 1;
            dep[u] = dep[fa] + 1;
            fat[u] = fa;
            for (auto v : g[u]) {
                if (v == fa) continue;
                dfsA(v, u);
                siz[u] += siz[v];
                if (siz[v] > siz[son[u]]) son[u] = v;
            }
        };
        dfsA(rt, 0);

        int dfncnt = 0;
        function<void(int, int)> dfsB = [&](int u, int tp) {
            top[u] = tp;
            dfn[++dfncnt] = u;
            L[u] = dfncnt;
            if (son[u]) dfsB(son[u], tp);
            for (auto v : g[u]) {
                if (v == fat[u] || v == son[u]) continue;
                dfsB(v, v);
            }
            R[u] = dfncnt;
        };
        dfsB(rt, rt);
    }

    int LCA(int u, int v) {
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            u = fat[top[u]];
        }
        return dep[u] < dep[v] ? u : v;
    }

    int dist(int u, int v) { return dep[u] + dep[v] - 2 * dep[LCA(u, v)]; }

    int jump(int u, int k) {
        if (dep[u] < k) return -1;
        int d = dep[u] - k;
        while (dep[top[u]] > d) u = fat[top[u]];
        return dfn[L[u] - dep[u] + d];
    }

    bool isAncester(int u, int v) { return L[u] <= L[v] && L[v] <= R[u]; }

    pair<int, int> path_intersection(pair<int, int> A, pair<int, int> B) {
        auto [u1, v1] = A;
        auto [u2, v2] = B;
        vector<int> lca = { LCA(u1,u2),LCA(u1,v2),LCA(v1,u2),LCA(v1,v2) };
        sort(lca.begin(), lca.end(), [&](int a, int b) {return dep[a] > dep[b];});
        int p1 = lca[0], p2 = lca[1];
        if (p1 == p2 && (dep[p1] < dep[LCA(u1, v1)] || dep[p1] < dep[LCA(u2, v2)])) return { -1,-1 };
        else return { p1,p2 };
    }
};

template<class T, class F>
class SegmentTree {
    int n;
    vector<T> node;

    void update(int rt, int l, int r, int x, F f) {
        if (r < x || x < l) return;
        if (l == r) return node[rt] = f(node[rt]), void();
        int mid = l + r >> 1;
        update(rt << 1, l, mid, x, f);
        update(rt << 1 | 1, mid + 1, r, x, f);
        node[rt] = node[rt << 1] + node[rt << 1 | 1];
    }

    T query(int rt, int l, int r, int x, int y) {
        if (r < x || y < l) return T();
        if (x <= l && r <= y) return node[rt];
        int mid = l + r >> 1;
        return query(rt << 1, l, mid, x, y) + query(rt << 1 | 1, mid + 1, r, x, y);
    }

public:
    SegmentTree(int _n = 0) { init(_n); }
    SegmentTree(const vector<T> &src) { init(src); }

    void init(int _n) {
        n = _n;
        node.assign(n << 2, T());
    }
    void init(const vector<T> &src) {
        assert(src.size() >= 2);
        init(src.size() - 1);
        function<void(int, int, int)> build = [&](int rt, int l, int r) {
            if (l == r) return node[rt] = src[l], void();
            int mid = l + r >> 1;
            build(rt << 1, l, mid);
            build(rt << 1 | 1, mid + 1, r);
            node[rt] = node[rt << 1] + node[rt << 1 | 1];
        };
        build(1, 1, n);
    }

    void update(int x, F f) { update(1, 1, n, x, f); }

    T query(int x, int y) { return query(1, 1, n, x, y); }
};
const int N = 1e5 + 7;
vector<int> g[N];
HLD hld;

struct T {
    int u = -1, v = -1;
    int du = -1, dv = -1;
    friend T operator+(const T &a, const T &b) {
        if (a.u == -1) return b;
        if (b.u == -1) return a;
        if (a.u == 0 || b.u == 0) return { 0,0,0,0 };

        auto [p1, p2] = hld.path_intersection({ a.u, a.v }, { b.u,b.v });
        if (p1 == -1) return { 0,0,0,0 };

        T ans = T();
        ans.u = p1;
        ans.v = p2;

        vector<int> dis;
        dis = { hld.dist(a.u, ans.u),hld.dist(a.u, ans.v),hld.dist(a.v, ans.u),hld.dist(a.v, ans.v) };
        if (dis[0] < dis[1]) {
            ans.du = max(ans.du, a.du + dis[0]);
            ans.dv = max(ans.dv, a.dv + dis[3]);
        }
        else {
            ans.du = max(ans.du, a.dv + dis[2]);
            ans.dv = max(ans.dv, a.du + dis[1]);
        }
        dis = { hld.dist(b.u, ans.u),hld.dist(b.u, ans.v),hld.dist(b.v, ans.u),hld.dist(b.v, ans.v) };
        if (dis[0] < dis[1]) {
            ans.du = max(ans.du, b.du + dis[0]);
            ans.dv = max(ans.dv, b.dv + dis[3]);
        }
        else {
            ans.du = max(ans.du, b.dv + dis[2]);
            ans.dv = max(ans.dv, b.du + dis[1]);
        }

        return ans;
    }
};
struct F {
    int u, v;
    T operator()(const T &x) { return { u,v,0,0 }; }
};

SegmentTree<T, F> sgt;

int find(int u, int v, int k) {
    int lca = hld.LCA(u, v);
    if (hld.dist(u, lca) < k) return hld.jump(v, hld.dist(u, v) - k);
    else return hld.jump(u, k);
}

int query(int l, int r) {
    auto [u, v, du, dv] = sgt.query(l, r);
    if (u == 0) return -1;
    int d = hld.dist(u, v);
    //! 下面不能用mid取下整判断,否则会出现两个(u,v)内中点,但只能选标号最小的那个,就会判断错误
    if (du >= d + dv) return u; // 中点(如果有两个,则会取到右侧中点)落在u及其左侧
    else if (dv >= d + du) return v; // 中点(如果有两个,则会取到左侧中点)落在v及其右侧
    int mid = du + d + dv >> 1;
    if ((du + d + dv) & 1) return min(find(u, v, mid - du), find(u, v, mid + 1 - du));
    else return find(u, v, mid - du);
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= n - 1;i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    hld.init(1, vector<vector<int>>(g, g + n + 1));

    vector<T> src(m + 1);
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        src[i] = { u,v,0,0 };
    }
    sgt.init(src);

    int q;
    cin >> q;
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int l, r;
            cin >> l >> r;
            cout << query(l, r) << '\n';
        }
        else {
            int x, u, v;
            cin >> x >> u >> v;
            sgt.update(x, { u,v });
        }
    }
    return 0;
}