虚树学习笔记

虚树

基本概念:

虚树是指在原树上选择若干点组成的树,它在原树的基础上做了一些简化,但是保留必要的信息,从而使得计算更加高效。
虚树主要用于树形DP中,能够减少顶点数,降低时间复杂度。

例题:

[SDOI2011] 消耗战

题目传送门

题目大意

给出一棵树,n个顶点。每条边有边权。有m次询问,每次询问给出k个询问点,问使得这
k个点均不与1号点(根节点)相连的最小代价。

分析:

这道题可以用树形dp来做:
显然:我们设\(dp[i]\)表示以\(i\)为根的子树内满足题意的最小代价,\(mind[i]\)表示点\(i\)到根节点的路径中最小边权是多少
1、点\(i\)是询问点:\(dp_i = mind_i\)
2、点\(i\)不是询问点:\(dp_i = min (mind_i , \sum dp_j(j是i的儿子子树j里面有询问点))\)
分析一下时间复杂度,发现是 O(n*m) 的,好像过不了
此时我们就要想一想 优化
因为我们每次其实只会考虑那些询问点,而询问点的数量满足 \(k<=500005\) ,所以我们花费了大多数时间在跑那些没有意义的节点。
所以我们需要 重建一棵树,使得树上所有节点都是有意义的,这就是虚树 即只包含所有的询问点和他们的\(lca\)

虚树维护

首先要给原树做一次\(dfs\),打上时间戳\(dfn\)

将要查询的点按照 \(dfn\) 排序,

然后搞一个栈,里面存的是一条 \(1\) 号点到当前点的链。

栈中的点之间暂未连边,最后退栈的时候再连边。

先给1号节点入栈,\(stk[++top] = 1\)
然后考虑当前待加入的节点\(p(p是询问点)\)
1、如果\(stk[top] == lca (p , stk[top])\) , 那么一定满足条件,直接进栈,\(stk[++top] = p\)。否则,\(p\)\(stk[top]\) 一定是不同子树里面的。

2、然后\(while(dfn[lca(stk[top] , p)] <= dfn[stk[top - 1]])\)​ 则连边并退栈 \(add(stk[top - 1] , stk[top]),top --\)。​

3、最后如果\(lca(stk[top] , p) \neq stk[top]\)​ ,那么连边 \((lca(stk[top] , p) , stk[top])\)​,将 \(lca\) 入栈 \(stk[top] = Lca(stk[top] , p)\)​。将 \(p\)​​ 入栈,然后退出。

把所有关键点处理完之后要把栈中的节点连边并退栈

\(while(top)\) 连边 \(add (stk[top - 1] , stk[top]) , top --\)

可能下一题的虚树代码更好看一点

code

#include<bits/stdc++.h>
#define fu(x , y , z) for (int x = y ; x <= z ; x ++) 
#define LL long long
#define fd(x , y , z) for (int x = y ; x >= z ; x --)
using namespace std;
const int N = 250005 , M = 5e5 + 5;
struct E {
    int to , nt;
    LL w;
} e[N << 1];
int p1[N] , p , dep[N] , top[N] , sz[N] , son[N] , hd[N] , cnt , fa[N] , tp , stk[N] , flg[N] , m , h[M] , k , u , v , n , h1;
LL dp[N] , mind[N] , wi;
void add (int x , int y , int z) { e[++cnt].to = y , e[cnt].nt = hd[x] , e[cnt].w = z , hd[x] = cnt; }
inline void dfs1 (int x) {
    int y , maxs = 0;
    sz[x] = 1;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (y == fa[x])
            continue;
        dep[y] = dep[x] + 1;
        fa[y] = x;
        mind[y] = min (mind[x] , e[i].w);
        dfs1 (y);
        sz[x] += sz[y];
        if (sz[y] > maxs) {
            maxs = sz[y];
            son[x] = y; 
        }
    }
}
inline void dfs2 (int x) {
    p1[x] = ++p;
    int y;
    if (son[x]) {
        top[son[x]] = top[x];   
        dfs2 (son[x]);
    }
    else 
        return;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (y == fa[x] || y == son[x]) 
            continue;
        top[y] = y;
        dfs2 (y);
    }
}
int Lca (int x , int y) {
    while (top[x] != top[y]) {
        if (dep[fa[top[x]]] > dep[fa[top[y]]]) 
            swap(x , y);
        y = fa[top[y]];
    }
    return dep[x] < dep[y] ? x : y;
}
inline void Insert (int x) {
    int y = stk[tp];
    int lca = Lca (x , y);
    while (dep[y] > dep[lca] && tp) {
        if (dep[stk[tp - 1]] < dep[lca]) {
            add (lca , y , 0);
        }
        else 
            add (stk[tp - 1] , stk[tp] , 0);
        tp--;
        y = stk[tp];
    }
    if (y == lca) {
        stk[++tp] = x;
    }
    else {
        stk[++tp] = lca;
        stk[++tp] = x;
    }
}
inline void dfs3 (int x) {
    int y;
    long long sum = 0;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (y == fa[x])
            continue;
        dfs3 (y);
        sum += dp[y];
    }
    if (flg[x]) {
        dp[x] = mind[x];
    }
    else {
        dp[x] = min (mind[x] , sum);
    }
    hd[x] = 0;
}
bool cmp (int x , int y) { return p1[x] < p1[y]; }
void solve () {
    int m;
    scanf ("%d" , &m);
    fu (T , 1 , m) {
        scanf ("%d" , &k);
        fu (i , 1 , k) scanf ("%d" , &h[i]);
        tp = 1;
        stk[1] = 1;
        cnt = 0;
        sort (h + 1 , h + k + 1 , cmp);

        fu (i , 1  ,k) {
            Insert (h[i]);
            flg[h[i]] = 1;
        }
        for (int i = tp ; i > 1 ; i --)
            add (stk[i-1] , stk[i] , 0);
        dfs3 (1);
        fu (i , 1 , k) flg[h[i]] = 0;
        printf ("%lld\n" , dp[1]);
    }
}
int main() {
    int u , v;
    LL w;
    dep[1] = 1;
    scanf ("%d" , &n);
    fu (i , 1 , n) mind[i] = 1e18;
    fu (i , 1 , n - 1) {
        scanf ("%d%d%d" , &u , &v , &w);
        add (u , v , w) , add (v , u , w);
    }
    dfs1 (1);
    dfs2 (1);
    cnt = 0;
    memset (hd , 0 , sizeof (hd));
    solve ();
    return 0;
}

CF613D Kingdom and its Cities

题目传送门

题目大意

给出一棵树,树上有 \(k\)​​ 个关键节点,你可以删掉树上的非关键点,请问使得所有的关键点互不连通最少需要删除的非关键点数

\(m\) 组询问,每次更新关键节点。

\(1 \le n \le 10^5 , 1 \le m \le 10 ^5 , \sum k\le10^5\)

分析

这里主要讲一下 \(dp\)

对于每个点维护

\(g[x]\) 为以 \(i\) 为根节点的子树中有没有关键点与 \(x\) 连通 如果x为关键点则 g[x] = 1

\(f[x]\) 为以 \(x\) 为根的答案

首先 \(f[x] = \sum_{v = son(x)} f[v]\)

然后分类讨论。

  • 如果 \(x\) 为关键点,那么 \(f[x] += \sum_{v = son(x)}g[v]\) ,也就是把所有有关键点的后代全部断掉。
  • 乳沟 \(x\) 不是关键点,如果它有多于一个的有关键点儿子,就把 \(x\) 断掉 \(f[x] ++ , g[x] = 0\) ,否则就留给祖先处理(可能跟其他的一起断掉更优)\(g[x] = 1\)

然后还是一样的虚树处理方式。

code

#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
#define fd(x , y , z) for(int x = y ; x >= z ; x --)
using namespace std;
const int N = 1e6 + 5 , K = 20;
int k , fa[N][K + 5] , dep[N] , n , m , rt , flg[N] , dfn[N] , pos , p[N] , f[N] , f1[N] , sz[N];
int stk[N] , top;
vector<int> g[N];
int read () {
    int val = 0;
    char ch = getchar ();
    while (ch < '0' || ch > '9') ch = getchar ();
    while (ch >= '0' && ch <= '9') {
        val = val * 10 + (ch - '0');
        ch = getchar ();
    }
    return val;
}
void add (int x , int y) { g[x].push_back(y); }
void pre (int x) {
    int y;
    dfn[x] = ++pos;
    for (auto i : g[x]) {
        if (i == fa[x][0]) continue;
        fa[i][0] = x;
        dep[i] = dep[x] + 1;
        pre (i);
    }
}
int Lca (int x , int y) {
    if (dep[x] < dep[y]) swap (x , y);
    fd (i , K , 0)
        if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
    if (x == y) return x;
    fd (i , K , 0) 
        if (fa[x][i] != fa[y][i]) x = fa[x][i] , y = fa[y][i];
    return fa[x][0];
}
bool cmp (int x , int y) { return dfn[x] < dfn[y]; }
void build () {
    top = 1;
    stk[1] = 1;
    g[1].clear();
    fu (i , 1 , k) {
        if (p[i] == 1) continue;
        int lca = Lca (p[i] , stk[top]);
        if (lca != stk[top]) {
            while (dfn[stk[top - 1]] > dfn[lca]) {
                add (stk[top - 1] , stk[top]);
                top --;
            }
            if (lca != stk[top - 1]) {
                g[lca].clear();
                add (lca , stk[top]);
                stk[top] = lca;
            }
            else add (lca , stk[top --]);
        }
        g[p[i]].clear() , stk[++top] = p[i];
    }
    fu (i , 1 , top - 1) add (stk[i] , stk[i + 1]);
}
void fans (int x) {
    f[x] = sz[x] = 0;
    for (auto i : g[x]) {
        fans (i);
        f[x] += f[i] , sz[x] += sz[i];
    }
    if (flg[x]) f[x] += sz[x] , sz[x] = 1;
    else if (sz[x] > 1) f[x] ++ , sz[x] = 0;
}
int main () {
    int u , v;
    n = read ();
    fu (i , 1 , n - 1) {
        u = read () , v = read ();
        add (u , v) , add (v , u);
    }
    dep[1] = 1;
    pre (1);
    fu (i , 1 , K) 
        fu (j , 1 , n) fa[j][i] = fa[fa[j][i - 1]][i - 1];
    m = read ();
    int x , bz;
    while (m --) {
        k = read ();
        fu (i , 1 , k) {
            p[i] = read ();
            flg[p[i]] = 1;
        }
        bz = 0;
        fu (i , 1 , k) {
            if (flg[fa[p[i]][0]]) {
                bz = 1;
                printf ("-1\n");
                break;
            }
        }
        if (bz == 1) {
            fu (i , 1 , k) flg[p[i]] = 0;
            continue;
        }
        sort (p + 1 , p + k + 1 , cmp);
        build ();
        fans (1);
        printf ("%d\n" , f[1]);
        fu (i , 1 , k) flg[p[i]] = 0;
    }
    return 0;
}

后记

2024.5.11做了虚树的专题,发现了多处错误,已改正,也添加了一些部分。

posted @ 2023-04-08 19:39  2020fengziyang  阅读(7)  评论(0编辑  收藏  举报