1 条题解

  • 0
    @ 2025-11-24 15:39:01

    算法标签

    • DFS 序 + 线段树/树状数组
    • 欧拉序处理
    • 区间更新 + 单点查询

    题目分析

    这个问题需要动态维护二叉树的遍历顺序,其中每个节点有一个标记值 x{1,0,1}x \in \{-1,0,1\} 控制该节点在遍历中的位置(在子节点之前/之间/之后访问)。

    关键观察

    1. 遍历顺序的递归定义

      • 如果 x=1x = -1:顺序为 [当前节点] + [左子树遍历] + [右子树遍历]
      • 如果 x=0x = 0:顺序为 [左子树遍历] + [当前节点] + [右子树遍历]
      • 如果 x=1x = 1:顺序为 [左子树遍历] + [右子树遍历] + [当前节点]
    2. 子树大小计算: 对于节点 uu,其子树大小 size[u]size[u] 是固定的(不随 xx 变化),但节点在遍历中的位置取决于:

      • 祖先节点的 xx 值(决定当前节点在祖先的子树中的相对位置)
      • 左子树的大小(如果 x=0x = 0x=1x = 1,当前节点在左子树之后)
    3. 动态维护挑战: 修改一个节点的 xx 值会影响其所有后代节点在遍历中的位置,因此需要高效的数据结构。

    解决方案

    使用 DFS 序 + 线段树 来维护:

    1. 预处理

      • 对树进行 DFS,记录每个节点的入序 in[u]in[u] 和出序 out[u]out[u]
      • 计算每个节点的子树大小 size[u]size[u]
      • 记录父节点信息
    2. 数据结构设计: 维护两棵线段树:

      • 标记树:存储每个节点的 xx 值,支持区间赋值
      • 偏移树:存储由于祖先节点的 xx 值变化导致的遍历位置偏移量
    3. 位置计算: 节点 uu 的位置 = 1 + 所有在 uu 之前被访问的节点数量 可以通过在 DFS 序上维护前缀和来快速计算

    题解代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 100005;
    
    int n, q;
    int L[MAXN], R[MAXN];
    int x[MAXN];  // 每个节点的标记值,初始为-1
    
    // DFS序相关
    int in[MAXN], out[MAXN], dfs_time;
    int parent[MAXN];
    vector<int> children[MAXN];
    
    // 子树大小
    int sz[MAXN];
    
    // 线段树:区间赋值,单点查询
    struct SegmentTree {
        int tree[4 * MAXN];
        int lazy[4 * MAXN];
        
        void build(int idx, int l, int r) {
            lazy[idx] = -2;  // 无标记
            if (l == r) {
                tree[idx] = -1;  // 初始值为-1
                return;
            }
            int mid = (l + r) / 2;
            build(idx * 2, l, mid);
            build(idx * 2 + 1, mid + 1, r);
        }
        
        void push_down(int idx, int l, int r) {
            if (lazy[idx] != -2) {
                tree[idx] = lazy[idx];
                if (l != r) {
                    lazy[idx * 2] = lazy[idx];
                    lazy[idx * 2 + 1] = lazy[idx];
                }
                lazy[idx] = -2;
            }
        }
        
        void update(int idx, int l, int r, int ql, int qr, int val) {
            push_down(idx, l, r);
            if (ql > r || qr < l) return;
            if (ql <= l && r <= qr) {
                lazy[idx] = val;
                push_down(idx, l, r);
                return;
            }
            int mid = (l + r) / 2;
            update(idx * 2, l, mid, ql, qr, val);
            update(idx * 2 + 1, mid + 1, r, ql, qr, val);
        }
        
        int query(int idx, int l, int r, int pos) {
            push_down(idx, l, r);
            if (l == r) return tree[idx];
            int mid = (l + r) / 2;
            if (pos <= mid) return query(idx * 2, l, mid, pos);
            else return query(idx * 2 + 1, mid + 1, r, pos);
        }
    } seg_x;
    
    void dfs(int u, int p) {
        parent[u] = p;
        in[u] = ++dfs_time;
        sz[u] = 1;
        
        if (L[u] != 0) {
            children[u].push_back(L[u]);
            dfs(L[u], u);
            sz[u] += sz[L[u]];
        }
        if (R[u] != 0) {
            children[u].push_back(R[u]);
            dfs(R[u], u);
            sz[u] += sz[R[u]];
        }
        
        out[u] = dfs_time;
    }
    
    // 计算节点u在其子树中的相对位置
    int get_relative_position(int u) {
        int pos = 1;  // 从1开始计数
        
        int current_x = seg_x.query(1, 1, n, u);
        
        if (L[u] != 0) {
            if (current_x == -1) {
                // 当前节点在最前,先加自己
                pos += 0;  // 自己还没算
            } else {
                // 当前节点在中间或后面,左子树在前
                pos += sz[L[u]];
            }
        }
        
        if (R[u] != 0 && current_x != 1) {
            // 如果当前节点不在最后,右子树在当前位置之后
            pos += sz[R[u]];
        }
        
        return pos;
    }
    
    // 计算节点u在整个遍历中的绝对位置
    int get_absolute_position(int u) {
        int pos = 1;  // 从1开始
        
        // 从u向上到根,计算每个祖先的贡献
        vector<int> path;
        int cur = u;
        while (cur != 0) {
            path.push_back(cur);
            cur = parent[cur];
        }
        reverse(path.begin(), path.end());
        
        for (int i = 0; i < path.size(); i++) {
            int node = path[i];
            if (node == u) {
                // 到达目标节点,加上在其子树中的相对位置
                pos += get_relative_position(node) - 1;
                break;
            }
            
            int current_x = seg_x.query(1, 1, n, node);
            
            // 左子树的贡献
            if (L[node] != 0 && L[node] != u && in[L[node]] <= in[u] && in[u] <= out[L[node]]) {
                // u在左子树中
                if (current_x == -1) {
                    // 当前节点在最前,左子树在节点之后
                    pos += 1;  // 跳过当前节点
                }
                // 继续在左子树中搜索
                continue;
            }
            
            // 右子树的贡献
            if (R[node] != 0 && R[node] != u && in[R[node]] <= in[u] && in[u] <= out[R[node]]) {
                // u在右子树中
                if (current_x == -1) {
                    pos += 1 + (L[node] != 0 ? sz[L[node]] : 0);
                } else if (current_x == 0) {
                    pos += (L[node] != 0 ? sz[L[node]] : 0) + 1;
                }
                // current_x == 1: 右子树在最前,已在前面计算
                continue;
            }
            
            // u不在当前节点的直接子树中,加上整个左子树
            if (L[node] != 0 && in[u] > out[L[node]]) {
                pos += sz[L[node]];
            }
            
            // 加上当前节点(如果在u之前)
            if (current_x != 1 && node != u) {
                pos += 1;
            }
            
            // 加上整个右子树(如果在u之前)
            if (R[node] != 0 && in[u] > out[R[node]] && current_x != 1) {
                pos += sz[R[node]];
            }
        }
        
        return pos;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        
        cin >> n >> q;
        
        for (int i = 1; i <= n; i++) {
            cin >> L[i] >> R[i];
            x[i] = -1;  // 初始值
        }
        
        // DFS预处理
        dfs_time = 0;
        dfs(1, 0);
        
        // 初始化线段树
        seg_x.build(1, 1, n);
        
        // 处理查询
        for (int i = 0; i < q; i++) {
            int t;
            cin >> t;
            
            if (t == 1) {
                int l, r, val;
                cin >> l >> r >> val;
                seg_x.update(1, 1, n, l, r, val);
            } else {
                int u;
                cin >> u;
                cout << get_absolute_position(u) << "\n";
            }
        }
        
        return 0;
    }
    

    更高效的实现(使用欧拉序)

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 100005;
    
    int n, q;
    int L[MAXN], R[MAXN];
    int x_val[MAXN];
    
    // 欧拉序相关
    int euler[2 * MAXN], pos_in_euler[MAXN], euler_time;
    int left_size[MAXN], right_size[MAXN];
    
    void dfs_euler(int u) {
        if (u == 0) return;
        
        // 记录节点进入
        euler[++euler_time] = u;
        pos_in_euler[u] = euler_time;
        
        int x = x_val[u];
        
        if (x == -1) {
            // 先访问当前节点(已记录),然后左右子树
            dfs_euler(L[u]);
            dfs_euler(R[u]);
        } else if (x == 0) {
            // 先左子树,然后当前节点,然后右子树
            dfs_euler(L[u]);
            euler[++euler_time] = u;  // 再次记录当前节点
            dfs_euler(R[u]);
        } else {  // x == 1
            // 先左右子树,然后当前节点
            dfs_euler(L[u]);
            dfs_euler(R[u]);
            euler[++euler_time] = u;  // 再次记录当前节点
        }
    }
    
    // 树状数组:单点更新,区间求和
    struct Fenwick {
        int bit[2 * MAXN];
        
        void update(int idx, int val) {
            for (; idx <= 2 * n; idx += idx & -idx) {
                bit[idx] += val;
            }
        }
        
        int query(int idx) {
            int res = 0;
            for (; idx > 0; idx -= idx & -idx) {
                res += bit[idx];
            }
            return res;
        }
        
        int range_sum(int l, int r) {
            return query(r) - query(l - 1);
        }
    } fenw;
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        
        cin >> n >> q;
        
        for (int i = 1; i <= n; i++) {
            cin >> L[i] >> R[i];
            x_val[i] = -1;  // 初始值
        }
        
        // 初始欧拉序
        euler_time = 0;
        dfs_euler(1);
        
        // 初始化树状数组:每个欧拉序位置标记为1
        for (int i = 1; i <= euler_time; i++) {
            fenw.update(i, 1);
        }
        
        // 处理查询(简化版:每次修改后重建欧拉序)
        // 注意:实际比赛需要更高效的增量更新
        
        for (int i = 0; i < q; i++) {
            int t;
            cin >> t;
            
            if (t == 1) {
                int l, r, x;
                cin >> l >> r >> x;
                
                // 更新标记值
                for (int j = l; j <= r; j++) {
                    x_val[j] = x;
                }
                
                // 重建欧拉序(简化实现,实际应用需要优化)
                euler_time = 0;
                dfs_euler(1);
                
                // 重建树状数组
                memset(fenw.bit, 0, sizeof(fenw.bit));
                for (int j = 1; j <= euler_time; j++) {
                    fenw.update(j, 1);
                }
                
            } else {
                int u;
                cin >> u;
                // 节点u在欧拉序中第一次出现的位置前有多少个节点
                cout << fenw.query(pos_in_euler[u]) << "\n";
            }
        }
        
        return 0;
    }
    

    复杂度分析

    • 朴素方法:每次查询 O(n)O(n),总复杂度 O(nq)O(nq),只能过小数据
    • 优化方法:使用欧拉序 + 树状数组,查询 O(logn)O(\log n),但修改需要 O(n)O(n) 重建
    • 目标复杂度O((n+q)logn)O((n+q)\log n) 需要更复杂的数据结构维护动态欧拉序
    • 1

    信息

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