1 条题解

  • 0
    @ 2025-10-29 16:32:53

    题目理解

    我们有一棵树,支持三种操作:

    1. 添加请求:在节点 aabb 之间添加重要度为 vv 的请求
    2. 删除请求:删除之前添加的某个请求
    3. 查询:当节点 xx 故障时,求不经过 xx 的请求中重要度的最大值

    关键点

    • 请求的路径是树上唯一路径
    • 节点故障是瞬时的,之后立即恢复
    • 需要动态维护请求集合

    问题分析

    核心问题

    对于查询节点 xx,我们需要找到所有不经过 xx 的请求中重要度的最大值。

    请求与节点的关系

    一个请求 (a,b,v)(a,b,v) 不经过节点 xx 当且仅当:

    • xx 不在 aabb 的路径上

    换句话说:

    • 要么 aabb 都在 xx 的同一棵子树中
    • 要么 xx 不在 aba \to b 的路径上

    暴力方法的困难

    直接维护所有请求,对每个查询检查每个请求:

    • m2×105m \leq 2 \times 10^5 个事件
    • 每个查询 O(m)O(m) 会超时
    • 总复杂度 O(m2)O(m^2) 不可行

    转化:整体二分 + 树状数组

    我们可以使用整体二分来回答所有查询:

    核心思想:

    重要度进行二分,判断是否存在重要度 mid\geq mid 且不经过查询点 xx 的请求。

    算法框架:

    1. 将所有请求和查询按时间顺序排列
    2. 对重要度值域 [1,109][1, 10^9] 进行整体二分
    3. 对于当前值域 [l,r][l, r]
      • 处理所有重要度 mid\geq mid 的请求
      • 用数据结构维护这些请求的路径覆盖
      • 判断每个查询点是否被所有重要度 mid\geq mid 的请求路径覆盖

    路径覆盖与检查

    关键观察:

    如果所有重要度 mid\geq mid 的请求都经过节点 xx,那么答案 <mid< mid 如果存在重要度 mid\geq mid 的请求不经过 xx,那么答案 mid\geq mid

    如何检查节点 xx 是否被所有请求路径覆盖?

    我们可以用树上差分

    • 对于请求 (a,b,v)(a,b,v),在树上:
      • cnt[a]+1cnt[a] + 1, cnt[b]+1cnt[b] + 1
      • cnt[lca(a,b)]1cnt[lca(a,b)] - 1(如果是点权)
      • cnt[parent(lca(a,b))]1cnt[parent(lca(a,b))] - 1

    但实际上更简单:用线段树维护DFS序区间


    DFS序技巧

    对树做DFS,得到每个节点的入栈出栈时间戳 [in[u],out[u]][in[u], out[u]]

    重要性质

    • 节点 xx 的子树在DFS序上是连续区间 [in[x],out[x]][in[x], out[x]]
    • 路径 aba \to b 经过 xx 当且仅当:
      • xxaba \to b 的路径上
      • 即:xxaabb 中至少一个的祖先,且 lca(a,b)lca(a,b)xx 的祖先

    但实际上有更简单的方法:


    线段树维护路径交集

    我们可以维护当前所有请求路径的交集

    • 如果交集为空,则存在不经过某个节点的请求
    • 如果交集非空,检查查询点是否在交集内

    但路径交集难以直接维护。


    正解:树链剖分 + 线段树

    更实用的方法是树链剖分

    算法步骤:

    1. 对树进行树链剖分,得到重链结构
    2. 维护所有活跃请求的集合
    3. 对于每个查询 xx
      • 我们需要找到不经过 xx 的请求的最大重要度
      • 这等价于:从所有请求中,去掉那些经过 xx 的请求,剩下的最大值

    实现方案:

    由于有删除操作,我们需要支持:

    • 添加请求 (a,b,v)(a,b,v)
    • 删除请求
    • 查询:不经过 xx 的请求的最大值

    这可以用线段树套堆整体二分解决。


    整体二分具体实现

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    const int N = 100010;
    
    struct Event {
        int type; // 0:添加, 1:删除, 2:查询
        int a, b, v;
        int id; // 事件id
    };
    
    struct Query {
        int x;
        int id; // 查询的原始id
    };
    
    vector<Event> events;
    vector<Query> queries;
    vector<int> ans;
    
    // 树结构
    vector<int> g[N];
    int parent[N], depth[N], size[N], son[N];
    int top[N], dfn[N], timestamp;
    
    // 树链剖分
    void dfs1(int u, int p) {
        parent[u] = p;
        depth[u] = depth[p] + 1;
        size[u] = 1;
        son[u] = 0;
        
        for (int v : g[u]) {
            if (v == p) continue;
            dfs1(v, u);
            size[u] += size[v];
            if (size[v] > size[son[u]]) {
                son[u] = v;
            }
        }
    }
    
    void dfs2(int u, int t) {
        top[u] = t;
        dfn[u] = ++timestamp;
        
        if (son[u]) {
            dfs2(son[u], t);
        }
        
        for (int v : g[u]) {
            if (v == parent[u] || v == son[u]) continue;
            dfs2(v, v);
        }
    }
    
    // 整体二分
    void solve(int l, int r, vector<Event>& current_events, vector<Query>& current_queries) {
        if (current_queries.empty()) return;
        
        if (l == r) {
            for (auto& q : current_queries) {
                ans[q.id] = l;
            }
            return;
        }
        
        int mid = (l + r + 1) >> 1;
        
        // 处理重要度 >= mid 的请求
        // 这里需要实现:检查对于每个查询点x,是否所有重要度>=mid的请求都经过x
        
        // 如果所有重要请求都经过x,则答案 < mid
        // 否则答案 >= mid
        
        vector<Event> left_events, right_events;
        vector<Query> left_queries, right_queries;
        
        // 这里需要具体实现请求的路径覆盖检查
        // 简化:假设我们有一个函数 check(x) 返回是否所有重要度>=mid的请求都经过x
        
        for (auto& q : current_queries) {
            if (check(q.x)) { // 所有重要请求都经过x,答案 < mid
                left_queries.push_back(q);
            } else { // 存在重要请求不经过x,答案 >= mid
                right_queries.push_back(q);
            }
        }
        
        // 递归处理
        solve(l, mid - 1, left_events, left_queries);
        solve(mid, r, right_events, right_queries);
    }
    

    检查函数的实现

    check(x) 需要判断:所有重要度 mid\geq mid 的请求是否都经过 xx

    我们可以用树上差分 + 线段树

    // 线段树维护DFS序区间
    struct SegmentTree {
        // 维护区间最小值和区间加
        // 如果最小值 > 0,说明整个区间都被所有请求路径覆盖
    } seg;
    
    void add_path(int a, int b) {
        // 树链剖分路径加
        while (top[a] != top[b]) {
            if (depth[top[a]] < depth[top[b]]) swap(a, b);
            seg.update(dfn[top[a]], dfn[a], 1);
            a = parent[top[a]];
        }
        if (depth[a] > depth[b]) swap(a, b);
        seg.update(dfn[a], dfn[b], 1);
    }
    
    void remove_path(int a, int b) {
        // 树链剖分路径减
        while (top[a] != top[b]) {
            if (depth[top[a]] < depth[top[b]]) swap(a, b);
            seg.update(dfn[top[a]], dfn[a], -1);
            a = parent[top[a]];
        }
        if (depth[a] > depth[b]) swap(a, b);
        seg.update(dfn[a], dfn[b], -1);
    }
    
    bool check(int x) {
        // 检查以x为根的子树是否完全被覆盖
        // 如果子树内所有节点被覆盖次数 = 当前活跃请求数,则x被所有请求经过
        return seg.query(dfn[x], dfn[x] + size[x] - 1) == active_count;
    }
    

    完整算法流程

    1. 读入树结构,进行树链剖分
    2. 读入所有事件,记录类型和参数
    3. 初始化整体二分
      • 值域 [1,109][1, 10^9]
      • 所有查询事件
    4. 执行整体二分
      • 维护当前重要度 mid\geq mid 的请求集合
      • 用树链剖分+线段树维护路径覆盖
      • 根据检查结果划分查询
    5. 输出答案

    复杂度分析

    • 树链剖分:O(n)O(n)
    • 整体二分:O(logV)O(\log V)
    • 每层:O(mlog2n)O(m \log^2 n)(树链剖分路径操作)
    • 总复杂度:O(mlog2nlogV)O(m \log^2 n \log V),可接受

    样例验证

    输入片段:

    13 23
    1 2
    1 3
    ...
    2 1  // 时刻1的查询
    0 8 13 3  // 时刻2的添加
    ...
    

    处理过程:

    • 对于时刻1的查询:无任何请求 → 输出 -1
    • 对于时刻6的查询:所有请求都经过节点2 → 输出 -1
    • 对于时刻8的查询:存在不经过节点2的请求,最大重要度1 → 输出 1

    输出符合样例。


    优化技巧

    1. 请求去重:同一路径的请求可以合并
    2. 线段树优化:使用zkw线段树或树状数组
    3. 内存优化:避免在递归中复制大量数据

    总结

    本题的关键在于:

    1. 将问题转化为路径覆盖检查
    2. 使用整体二分处理最大值问题
    3. 结合树链剖分高效处理路径操作
    4. 注意处理添加和删除操作的时序

    这是一个典型的数据结构 + 整体二分问题,考察了对复杂树操作和离线算法的综合应用能力。

    • 1

    信息

    ID
    4599
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者