虚树学习笔记

2 篇文章 0 订阅

虚树

基本概念:

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

例题:

[SDOI2011] 消耗战

题目传送门

题目大意

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

分析:

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

虚树维护

首先要给原树做一次 d f s dfs dfs,打上时间戳 d f n dfn dfn

将要查询的点按照 d f n dfn dfn 排序,

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

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

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

2、然后 w h i l e ( d f n [ l c a ( s t k [ t o p ] , p ) ] < = d f n [ s t k [ t o p − 1 ] ] ) while(dfn[lca(stk[top] , p)] <= dfn[stk[top - 1]]) while(dfn[lca(stk[top],p)]<=dfn[stk[top1]])​ 则连边并退栈 a d d ( s t k [ t o p − 1 ] , s t k [ t o p ] ) , t o p − − add(stk[top - 1] , stk[top]),top -- add(stk[top1],stk[top]),top。​

3、最后如果 l c a ( s t k [ t o p ] , p ) ≠ s t k [ t o p ] lca(stk[top] , p) \neq stk[top] lca(stk[top],p)=stk[top]​ ,那么连边 ( l c a ( s t k [ t o p ] , p ) , s t k [ t o p ] ) (lca(stk[top] , p) , stk[top]) (lca(stk[top],p),stk[top])​,将 l c a lca lca 入栈 s t k [ t o p ] = L c a ( s t k [ t o p ] , p ) stk[top] = Lca(stk[top] , p) stk[top]=Lca(stk[top],p)​。将 p p p​​ 入栈,然后退出。

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

w h i l e ( t o p ) while(top) while(top) 连边 a d d ( s t k [ t o p − 1 ] , s t k [ t o p ] ) , t o p − − add (stk[top - 1] , stk[top]) , top -- add(stk[top1],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 k k​​ 个关键节点,你可以删掉树上的非关键点,请问使得所有的关键点互不连通最少需要删除的非关键点数

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

1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 1 0 5 , ∑ k ≤ 1 0 5 1 \le n \le 10^5 , 1 \le m \le 10 ^5 , \sum k\le10^5 1n105,1m105,k105

分析

这里主要讲一下 d p dp dp

对于每个点维护

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

f [ x ] f[x] f[x] 为以 x x x 为根的答案

首先 f [ x ] = ∑ v = s o n ( x ) f [ v ] f[x] = \sum_{v = son(x)} f[v] f[x]=v=son(x)f[v]

然后分类讨论。

  • 如果 x x x 为关键点,那么 f [ x ] + = ∑ v = s o n ( x ) g [ v ] f[x] += \sum_{v = son(x)}g[v] f[x]+=v=son(x)g[v] ,也就是把所有有关键点的后代全部断掉。
  • 乳沟 x x x 不是关键点,如果它有多于一个的有关键点儿子,就把 x x x 断掉 f [ x ] + + , g [ x ] = 0 f[x] ++ , g[x] = 0 f[x]++,g[x]=0 ,否则就留给祖先处理(可能跟其他的一起断掉更优) g [ x ] = 1 g[x] = 1 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做了虚树的专题,发现了多处错误,已改正,也添加了一些部分。

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值