福建WC2014 路径权值(Kruskal重构树 + 树状数组)

题目描述:

给定一个带权树,树上任意两点间的路径权值 \(d\left(x,y\right)\) 定义为 \(x,y\) 这两个点之间路径上的最小值,树上任意一点 \(x\) 的权值定义为这个点到树上其他所有点的路径权值和,即 \(\sum d\left(x,i\right)\),现求树上每一个点的路径权值和。

Input Format

首先输入一个整数 \(n\left(1 \leq n \leq 10^5 \right)\) ,表示树的点的个数。 
接下来 \(n−1\) 行,每行三个整数 \(x,y,s\left(1\leq x,y\leq n,1\leq s \leq 1000\right)\) ,表示编号为 \(x\) 的节点和编号为 \(y\) 的节点之间存在一条权值为 \(s\) 的边,树上每个点的编号为 \(1\sim n\)

Output Format

n行,每行输出每个节点对应的路径权值和

Sample Input

    4
    1 2 2
    2 4 1
    2 3 1

Sample Output

    4
    4
    3
    3

思路:

首先明确要求的是一个 \(x \rightarrow y\) 的所有路径的最小权值,而树链剖分是求两点路径上的最小 边权或点权行不通。要知道路径的最小值,还可以用 Kruskal重构树 来解决,根据重构树的重要性质可以知道,树上任意两点的最短路径的权值就是这两点在重构树上的 \(lca\) ,那么就可以在 \(O\left(\log{n}\right)\) 的时间复杂度下快速求出路径权值。

    /*读入部分*/
    int n; std::cin >> n;
    int now = n;
    std::vector<std::array<int, 3>> edge;
    std::vector<std::vector<int>> adj(n * 2);
    std::vector<int> val(n * 2);
    for (int i = 1; i < n; i++) {
        int u, v, w;
        std::cin >> u >> v >> w;
        edge.push_back({w, u, v});
    }

    /*Ex_kruskal*/
    DSU uf(n * 2 + 1);
    std::sort(edge.begin(), edge.end(), std::greater<std::array<int, 3>>());

    for (auto& [w, u, v] : edge) {
        int fu = uf.leader(u), fv = uf.leader(v);
        val[++now] = w;
        uf.f[fu] = uf.f[fv] = now;
        adj[now].push_back(fu), adj[now].push_back(fv);
    }

如果是一个点一个点的去计算过去,就会是 \(O\left(n ^ 2 \log{n} \right)\) 的复杂度很明显是会超时的。就要考虑如果优化掉每一次枚举的 \(O\left(n ^ 2\right)\) 的瓶颈。由于原来最大生成树的所有点在重构树中都是叶子节点,其余所有的节点都是代表着原来生成树中的边权。根据这一点可以知道,任何一个节点要到另一个节点一定是从左(右)子树经过该子树的根节点走向右(左)子树的,所以每一个非叶子节点都会对以它为根的子树中所有的节点产生 \(x\) 的贡献,也就是一个区间加的操作,可以用树状数组或线段树来实现,从而将计算每一个点到其他所有点的路径之和从暴力枚举的 \(O\left(n ^ 2\right)\) 优化成了 \(O\left(n\log{n}\right)\)

    /*区间加法*/
    auto change = [&](int x, int v) -> void {
        bit.add(seg[x][0], v);
        bit.add(seg[x][1] + 1, -v);
    };

    for (int i = n + 1; i <= now; i++) {
        change(adj[i][0], siz[adj[i][1]] * val[i]);
        change(adj[i][1], siz[adj[i][0]] * val[i]);
    }

    for (int i = 1; i <= n; i++) std::cout << bit.sum(seg[i][0]) << "\n";

在计算贡献的时候还需要知道左右子树的大小,所以还需要遍历一遍重构树来计算出每一棵子树的大小,左子树中所有的叶子节点都可以到右子树中,所以右子树对左子树的贡献就是 \(val_x × size\left[右子树\right]\) 左子树同理。总的时间复杂度为 \(O\left(n \log{n}\right)\) ,省去了每一次要求两点 \(LCA\)\(O\left(\log{n}\right)\)

    std::vector<int> siz(now + 1);
    std::vector<std::array<int, 2>> seg(now + 1);
    int idx = 0;

    std::function<void(int)> dfs = [&](int u) -> void {
        siz[u] = (u <= n); 
        seg[u][0] = ++idx;  //记录这棵子树的dfs序的范围
        for (auto& v : adj[u]) {
            dfs(v);
            siz[u] += siz[v];
        }
        seg[u][1] = idx;
    };

    dfs(now);

完整代码

    #include <bits/stdc++.h>

    using i64 = long long;

    #define rep(i,a,b) for (int i = a; i < b; i++)
    #define per(i,a,b) for (int i = a; i >= b; i--)
    #define SZ(s) int(s.size())
    #define all(v) v.begin(), v.end()

    template <typename T>
    struct Fenwick {
        const int n;
        std::vector<T> tr;
        Fenwick(int n) : n(n), tr(n + 1) {}

        void add(int x, T v) {
            for (int i = x; i <= n; i += i & -i) 
                tr[i] += v;
        }

        T sum(int x) {
            T ans = 0;
            for (int i = x; i; i-=i&-i) ans += tr[i];
            return ans;
        }
        
        T rangeSum(int l, int r) {return sum(r) - sum(l);}
        
        int query(T s) { // 查询1~pos的和小于等于s
            int pos = 0;
            for (int j = 18; j >= 0; j -- ) 
                if ((pos + (1ll << j) < n) && tr[pos + (1ll << j)] <= s) 
                    pos = (pos + (1 << j)), s -= tr[pos];
            return pos;
        }
    };

    struct DSU {  
        std::vector<int> f, siz;  
        DSU(int n) : f(n), siz(n, 1) { std::iota(f.begin(), f.end(), 0); }  
    
        int leader(int x) {  
            while (x != f[x]) x = f[x] = f[f[x]];  
            return x;  
        }  
    
        bool same(int x, int y) { return leader(x) == leader(y); }  
    
        bool merge(int x, int y) {  
            x = leader(x);  
            y = leader(y);  
            if (x == y) return false;  
            siz[x] += siz[y];  
            f[y] = x;
            return true;  
        }  
        
        int size(int x) { return siz[leader(x)]; }  
    };

    signed main() {
        std::cin.tie(nullptr)->sync_with_stdio(false);
        int n; std::cin >> n;
        int now = n;
        std::vector<std::array<int, 3>> edge;
        std::vector<std::vector<int>> adj(n * 2);
        std::vector<int> val(n * 2);
        for (int i = 1; i < n; i++) {
            int u, v, w;
            std::cin >> u >> v >> w;
            edge.push_back({w, u, v});
        }

        DSU uf(n * 2 + 1);
        std::sort(edge.begin(), edge.end(), std::greater<std::array<int, 3>>());

        for (auto& [w, u, v] : edge) {
            int fu = uf.leader(u), fv = uf.leader(v);
            val[++now] = w;
            uf.f[fu] = uf.f[fv] = now;
            adj[now].push_back(fu), adj[now].push_back(fv);
        }
        Fenwick<int> bit(now);

        std::vector<int> siz(now + 1);
        std::vector<std::array<int, 2>> seg(now + 1);
        int idx = 0;

        std::function<void(int)> dfs = [&](int u) -> void {
            siz[u] = (u <= n);
            seg[u][0] = ++idx;
            for (auto& v : adj[u]) {
                dfs(v);
                siz[u] += siz[v];
            }
            seg[u][1] = idx;
        };

        dfs(now);

        auto change = [&](int x, int v) -> void {
            bit.add(seg[x][0], v);
            bit.add(seg[x][1] + 1, -v);
        };

        for (int i = n + 1; i <= now; i++) {
            change(adj[i][0], siz[adj[i][1]] * val[i]);
            change(adj[i][1], siz[adj[i][0]] * val[i]);
        }

        for (int i = 1; i <= n; i++) std::cout << bit.sum(seg[i][0]) << "\n";
        return 0 ^ 0;
    }
posted @ 2022-11-03 20:37  浅渊  阅读(262)  评论(0)    收藏  举报