20250706 haosen 树论总结

20250706 haosen 树论总结

CF516D Drazil and Morning Exercise

图片

首先fx可以直接求出来,这个是显然的。

我们发现,fx最小的点一定在直径上,具体地,在直径的中点。

我们以他为根,我们就发现,fi随着树的深度递增。

那就可以直接双指针跑一下即可。

在f值从大到小排序后的节点序列上做双指针,维护若干个合法连通快,按f降序插入,清洗连通块使得满足差条件,然后合并连通块(当前点寻找up,也就是相邻的更小的),统计答案。

这个比较难理解,可以手摸一下。


CF1628E Groceries in Meteor Town

给定 N 个点的树,起初每个节点都是黑色。

三种操作:

把下标为 [l,r] 的点染成白色;
把下标为 [l,r] 的点染成黑色;
询问从节点 x 出发到达任意一个白色节点的简单路径上经过的边,最大可能的权值。不存在则输出-1.

考虑Kruskal重构树

于是查询操作就转化成了重构树上, x 点与所有白点的 lca 的权值。

你可以直接拍到dfn序上,用线段树维护区间最大和最小dfn,这就是答案。


这题我得放一下代码


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

const int N = 3e5 + 5;
const int INF = 1e9;

int n, q;

// 链式前向星
struct Edge {
    int to, next;
} e[N << 2];
int head[N << 1], ecnt;

void add_edge(int u, int v) {
    e[++ecnt] = {v, head[u]};
    head[u] = ecnt;
}

// Kruskal重构树
namespace KruskalTree {
    struct EDGE {
        int u, v, w;
        EDGE(int u = 0, int v = 0, int w = 0) : u(u), v(v), w(w) {}
        bool operator<(const EDGE &other) const {
            return w < other.w;
        }
    };
    
    EDGE edges[N];
    int pcnt;  // 新增节点计数
    int fa[N << 1];
    int val[N << 1];  // 重构树节点权值
    
    int find(int x) {
        return x != fa[x] ? fa[x] = find(fa[x]) : x;
    }
    
    void add(int u, int v, int w, int i) {
        edges[i] = EDGE(u, v, w);
    }
    
    void build() {
        // 初始化并查集
        for (int i = 1; i <= (n << 1); i++) {
            fa[i] = i;
        }
        
        // 按边权排序
        sort(edges + 1, edges + n);
        
        // 构建重构树
        for (int i = 1; i < n; i++) {
            int x = find(edges[i].u);
            int y = find(edges[i].v);
            
            int newNode = n + (++pcnt);
            fa[x] = fa[y] = newNode;
            
            add_edge(newNode, x);
            add_edge(newNode, y);
            
            val[newNode] = edges[i].w;
        }
    }
}

// LCA和DFS序
namespace LCA {
    int dfncnt;
    int dfn[N << 1];  // 节点对应的DFS序
    int idfn[N << 1]; // DFS序对应的节点
    int dep[N << 1];
    int lg[N << 1];
    int f[N << 1][21];
    
    void dfs(int u, int fa) {
        dfn[u] = ++dfncnt;
        idfn[dfncnt] = u;
        f[u][0] = fa;
        dep[u] = dep[fa] + 1;
        
        for (int i = 1; i <= lg[dep[u]]; i++) {
            f[u][i] = f[f[u][i-1]][i-1];
        }
        
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (!dfn[v]) {
                dfs(v, u);
            }
        }
    }
    
    int lca(int x, int y) {
        if (dep[x] < dep[y]) swap(x, y);
        
        for (int i = lg[dep[x]]; i >= 0; i--) {
            if (dep[f[x][i]] >= dep[y]) {
                x = f[x][i];
            }
        }
        
        if (x == y) return x;
        
        for (int i = lg[dep[x]]; i >= 0; i--) {
            if (f[x][i] != f[y][i]) {
                x = f[x][i];
                y = f[y][i];
            }
        }
        
        return f[x][0];
    }
    
    void init() {
        // 预处理log2
        for (int i = 1; i <= (n << 1); i++) {
            lg[i] = lg[i-1] + ((1 << lg[i-1]) == (i >> 1));
        }
        
        // 从根节点开始DFS
        dfs(n + KruskalTree::pcnt, 0);
    }
}

// 线段树维护区间开放商店的DFS序范围
namespace SegmentTree {
    #define lc(i) (i << 1)
    #define rc(i) (i << 1 | 1)
    
    struct Range {
        int maxn, minn;
        Range(int maxn = -INF, int minn = INF) : maxn(maxn), minn(minn) {}
        void update(int x) {
            maxn = max(maxn, x);
            minn = min(minn, x);
        }
    };
    
    struct Node {
        int l, r, tag;  // tag: 0-无标记, 1-全开, 2-全关
        Range val, all; // val-开放商店范围, all-所有节点范围
        Node(int l = 0, int r = 0, int tag = 0) : l(l), r(r), tag(tag) {}
    };
    
    Node tr[N << 2];
    
    Range merge(const Range &a, const Range &b) {
        return Range(max(a.maxn, b.maxn), min(a.minn, b.minn));
    }
    
    void apply(int i, int tag) {
        tr[i].tag = tag;
        if (tag == 1) {
            tr[i].val = tr[i].all;  // 开放所有商店
        } else if (tag == 2) {
            tr[i].val = Range(-INF, INF);  // 关闭所有商店
        }
    }
    
    void push_down(int i) {
        if (tr[i].tag) {
            apply(lc(i), tr[i].tag);
            apply(rc(i), tr[i].tag);
            tr[i].tag = 0;
        }
    }
    
    void push_up(int i) {
        tr[i].val = merge(tr[lc(i)].val, tr[rc(i)].val);
    }
    
    void build(int l, int r, int i = 1) {
        tr[i] = Node(l, r);
        if (l == r) {
            tr[i].all.update(LCA::dfn[l]);
            return;
        }
        
        int mid = (l + r) >> 1;
        build(l, mid, lc(i));
        build(mid + 1, r, rc(i));
        tr[i].all = merge(tr[lc(i)].all, tr[rc(i)].all);
    }
    
    void update(int l, int r, int tag, int i = 1) {
        if (tr[i].l > r || tr[i].r < l) return;
        
        if (tr[i].l >= l && tr[i].r <= r) {
            apply(i, tag);
            return;
        }
        
        push_down(i);
        update(l, r, tag, lc(i));
        update(l, r, tag, rc(i));
        push_up(i);
    }
    
    Range query() {
        return tr[1].val;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> q;
    
    // 读入边并构建Kruskal重构树
    for (int i = 1; i < n; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        KruskalTree::add(u, v, w, i);
    }
    
    KruskalTree::build();
    LCA::init();
    SegmentTree::build(1, n);
    
    // 处理查询
    for (int i = 0; i < q; i++) {
        int type;
        cin >> type;
        
        if (type == 1 || type == 2) {
            int l, r;
            cin >> l >> r;
            SegmentTree::update(l, r, type);
        } else {
            int x;
            cin >> x;
            
            auto range = SegmentTree::query();
            range.update(LCA::dfn[x]);
            
            if (range.maxn == range.minn) {
                // 只有查询点本身开放或没有开放的商店
                cout << -1 << "\n";
            } else {
                int lca_node = LCA::lca(LCA::idfn[range.maxn], LCA::idfn[range.minn]);
                if (x == lca_node) {
                    cout << -1 << "\n";
                } else {
                    cout << KruskalTree::val[lca_node] << "\n";
                }
            }
        }
    }
    
    return 0;
}

CF566E Restoring Map

有一棵 n 个点的树,你不知道这棵树的边是怎么连的。

你得到了 n 条关于每个点信息,每条信息记录了距离某一个点 ≤2 的所有点。

但你不知道每条信息具体是哪个点的。

你需要构造一棵满足这些信息的树。


发现

非叶子节点x,y之间有边,当且仅当有两个集合(这里千万别理解错了,不然题就做不了了)交集为x,y,于是可以得到所有非叶节点之间的连边

我们知道每个点是否是叶子节点,接下来考虑如何找到叶子节点在树中的父亲。

显然,对于每个叶子节点,在所有包含它的集合中,节点数最少的集合一定就是此叶子节点的集合。因此我们可以确定每个叶子节点对应的集合是哪个。

一个叶子节点的集合中去掉所有叶子节点等于其父亲的连边集合


其他边界条件可以直接特判。


posted @ 2025-07-06 21:22  Dreamers_Seve  阅读(10)  评论(0)    收藏  举报