• Virtual Tree 学习笔记


    ( ext{Virtual Tree}) 略解

    引子

    像大部分虚树介绍一样,以一道 烂大街 的例题 [SDOI2011]消耗战 引入:

    (Description)

    给定一棵大小为 (n) 的树,每条边有边权,(m) 组询问,每次询问给定 (k)关键点,要求切断 (1) 与所有关键点的路径,求最小代价。

    ( ext{Data Constraint})

    (2 leq n leq 2.5 imes 10^5)(m geq 1)(sum{k} leq 5 imes 10^5)(1 leq k leq n - 1)

    ( ext{Simple Solution})

    (f_u) 表示切断 (u) 与其子树中所有 关键点 的最小代价。

    (w_{u, v}) 表示边 ((u, v)) 的权值。

    枚举 (u) 的儿子 (v),转移分为两类:

    • (v) 是关键点:(f_u = f_u + w(u, v))
    • (v) 不是关键点:(f_u = f_u + min(f_v, w(u, v)))

    朴素做法的时间复杂度为 (O(nq)),并过不掉这题,所以我们需要进行优化。

    可以注意到,我们浪费的很多时间 (dp) 非关键点,并且关键点的总数是与 (n) 同阶的,所以考虑能否 浓缩信息,大树变小

    这时引出 虚树 的概念。

    简介

    比较难讲清,其实主要是我菜,所以直观的感受一下:

    vtree-1

    选取不同关键点,建出来的虚树如下所示:

    vtree-3

    vtree-4

    vtree-5

    vtree-6

    (以上图转自 OI Wiki

    任意两个关键点的 (LCA) 也需要保存重要信息,我们需要将其保留,所以 虚树中不一定只包含关键点

    以及我们可以发现,虚树中祖先后代关系并不会改变。

    算法流程

    由于我比较菜,接下来直接搬运一波建树方法:

    首先很直观的,可以将所有关键点按 (DFS) 序排序,遍历一遍,两两间求求 (LCA),判判重,连连边,就建完啦!(逃)

    朴素算法复杂度较高,考虑单调栈,单调栈的作用是:维护虚树上的一条链

    为了方便,首先将根节点加入栈中,然后按 (DFS) 序遍历关键点:

      1. 若当前点 (u) 与栈顶节点的 (LCA) 是栈顶节点,那么说明 (u) 与栈中节点在一条链上,直接将 (u) 压入栈中
      1. (LCA) 不是栈顶节点,
      • 首先进行退栈,每弹出一个栈顶节点,在虚树中加入其与其虚树中父亲的连边。
      • 直到栈顶节点为 (LCA) 的父亲或为 (LCA),若栈顶节点不为 (LCA),则将 (LCA) 加入栈中
      • 最后将 (u) 加入栈中。

    需注意一些细节:

    退栈时,一般栈顶节点在虚树中的父亲为第二节点,但是最后弹出的栈顶节点的父亲为 (LCA) (注意我们最后把 (LCA) 加入栈中了);

    如例题,我们需要多次建虚树,则每次需要清空存图的数组,一般在节点入栈时清空即可(如邻接表,前向行 (head) 数组等);

    如例题,每一条虚树中的边(或点)可能浓缩的原树中几条边的信息,所以我们有时需要先求出浓缩信息,然后在连边赋上。例题中我用了倍增的方法在连边时 (logn) 求出一条虚树上边的权值(其实就是几条原树边取个 (min))。

    最后

    对于例题,建出虚树后,直接在上面跑朴素 (dp) 就可以啦!

    例题

    [SDOI2011]消耗战

    (Code)

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    #define N 1000000
    #define L 19
    
    #define Fo(i, u) for(int i = head[u]; i; i = edge[i].next)
    #define fo(i, x, y) for(int i = x, end_##i = y; i <= end_##i; ++ i)
    #define fd(i, x, y) for(int i = x; i >= y; i --)
    
    typedef long long ll;
    
    void read(int &x) {
        char ch = getchar(); x = 0;
        while (ch < '0' || ch > '9') ch = getchar();
        while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + ch - 48, ch = getchar();
    }
    
    struct EDGE { int next, to, w; } edge[N << 1];
    
    int head[N + 1], pre[N + 1][L + 1], g[N + 1][L + 1], sta[N + 1], dfn[N + 1], a[N << 1], dep[N + 1], key[N + 1];
    
    ll f[N + 1];
    
    int n, m, len, now = 0;
    
    int cnt_edge = 1;
    void Add(int u, int v, int w) { edge[ ++ cnt_edge ] = (EDGE) { head[u], v, w }, head[u] = cnt_edge; }
    void Link(int u, int v, int w) { Add(u, v, w), Add(v, u, w); }
    
    void Input() {
        read(n);
        for (int i = 1, x, y, z; i < n; i ++)
            read(x), read(y), read(z), Link(x, y, z);
        read(m);
    }
    
    int tot_dfn = 0;
    
    void Dfs1(int u, int la) {
        pre[u][0] = edge[la].to, g[u][0] = edge[la].w;
        dfn[u] = ++ tot_dfn, dep[u] = dep[pre[u][0]] + 1;
        Fo(i, u) if (i != la)
            Dfs1(edge[i].to, i ^ 1);
    }
    
    void Init() {
        Dfs1(1, 0);
        fo(j, 1, L) fo(i, 1, n)
            pre[i][j] = pre[pre[i][j - 1]][j - 1],
            g[i][j] = min(g[i][j - 1], g[pre[i][j - 1]][j - 1]);
    }
    
    bool Cmp(int x, int y) { return dfn[x] < dfn[y]; }
    
    int Get_lca(int u, int v) {
        if (dep[u] < dep[v]) swap(u, v);
        for (int i = 0, x = dep[u] - dep[v]; x; ++ i, x >>= 1)
            if (x & 1) u = pre[u][i];
        if (u == v) return u;
        fd(i, L, 0) if (pre[u][i] != pre[v][i])
            u = pre[u][i], v = pre[v][i];
        return pre[u][0];
    }
    
    int Get_dis(int u, int v) {
        if (dep[u] < dep[v]) swap(u, v);
        int Dis = g[u][0];
        for (int i = 0, x = dep[u] - dep[v]; x; ++ i, x >>= 1)
            if (x & 1) Dis = min(Dis, g[u][i]), u = pre[u][i];
        return Dis;
    }
    
    ll min(ll a, ll b) { return a < b ? a : b; } 
    
    void Build() {
        sort(a + 1, a + 1 + len, Cmp);
        sta[1] = 1, head[1] = 0; cnt_edge = 0;
        int top = 1;
        for (int i = 1, lca = 0; i <= len; i ++) {
            // if (a[i] == 1) continue; 以防重复加入
            lca = Get_lca(sta[top], a[i]);
            if (lca != sta[top]) {
                while (dfn[lca] < dfn[sta[top - 1]])
                    Add(sta[top - 1], sta[top], Get_dis(sta[top], sta[top - 1])), -- top;
                if (dfn[lca] > dfn[sta[top - 1]])
                    head[lca] = 0, Add(lca, sta[top], Get_dis(sta[top], lca)), sta[top] = lca;
                else
                    Add(lca, sta[top], Get_dis(sta[top], lca)), -- top;
            }
            sta[ ++ top ] = a[i], head[a[i]] = 0;
        }
        fo(i, 1, top - 1)
            Add(sta[i], sta[i + 1], Get_dis(sta[i + 1], sta[i]));
    }
    
    void Dfs2(int u) {
        f[u] = 0;
        Fo(i, u) {
            Dfs2(edge[i].to);
            f[u] += key[edge[i].to] == now ? edge[i].w : min(f[edge[i].to], edge[i].w);
        }
    }
    
    void Solve() {
        Build();
        Dfs2(1);
        printf("%lld
    ", f[1]);
    }
    
    int main() {
        Input();
        Init();
        fo(Case, 1, m) {
            read(len); ++ now;
            fo(i, 1, len)
                read(a[i]), key[a[i]] = now;
            Solve();
        }
    
        return 0;
    }
    
    

    (CF 613 D. Kingdom and its Cities)

    (Solution)

    待补充......

  • 相关阅读:
    #{}和${}的区别
    Shiro 的优点
    SpringSecurity 和 Shiro的之间的比较
    shiro的组件
    Apache Shiro 的三大核心组件
    Maven的工程类型有哪些?
    Maven仓库是什么
    什么是Maven?
    shiro有哪些组件?
    Apache Shiro 的三大核心组件
  • 原文地址:https://www.cnblogs.com/zhouzj2004/p/13976008.html
Copyright © 2020-2023  润新知