学习笔记 - 虚树

虚树 - 学习笔记

初学的时候整个人是懵的
不过总算是弄懂了


『算法概述』

对于一类树上的问题,如果仅有一部分点对答案“有用”(也就是说另一部分“可以不要”),那么我们考虑只存储那些有用的点。这就是虚树的思想。所以为什么它叫虚树?我也不知道……
通常情况下我们把一些点的 LCA 也算作“有用”的点。
虚树可做的题目一般来说(以我对虚树短浅的认知)会限制有用的点的总个数,并且会用多组询问的方式来使一般的方法TLE……


『具体实现』

仿照着其他博客的思路,我觉得用一道例题来讲会更加容易理解——

〔BZOJ 2286 消耗战〕
「题意」
给定一棵n个点的树,每个边有权值表示将它割断的花费,已知其中的k个点是有价值的,现在需要把这些点与点1断开(不连通),求最小花费。
输入时先给定一棵树。
m组数据,每次询问给出k个点,表示它们是有价值的,对于每组数据输出最小花费。
[反正\(O(nm)\)会超时就对了,保证所有数据的k之和不超过500000]

这道题最基础的做法是对于每一组数据跑一遍DP,显然是 \(O(nm)\) 的做法,会 TLE。
那么这道题也算是一道比较特殊的虚树题(某taotao给我说的:这道题必须把根节点1放在虚树里,就不能体现虚树的一些性质)。

「DP部分」

不难想到将点u与根节点割开无非就是割去根节点到u的路径上的一条边,根据这一点我们定义 dp[u] 表示 根节点到u的路径上的最小边,也就是将 u 从根节点割开的最小花费。
那么就可以得到简单的转移式,首先初始值是 \(dp[1]=INF\)(感性理解就是1不可能与它本身割开),然后转移式:
\[ u是v的父亲节点;\ (u,v)表示u到v的边权\\ dp[v]=\min\{dp[u],(u,v)\}\]
也就是说要么在 1 到 u 的路径上割掉一条边,要么割断 u 到 v 的边。

「虚树部分」

为什么说“一些点的lca有用”呢?等会在「求解部分」会解释。
虚树的构建方法大概是:

  1. 将有用点按 dfs 序排序;
  2. 创建栈并将第一个点压入;
  3. 枚举下一个点 u (直到枚举完为止);
  4. 如果栈内只有一个点,将 u 压入栈,跳到步骤 3,否则进行下一步;
  5. 求 u 与 栈顶点 的lca;
  6. 如果lca就是栈顶点,跳转到步骤3,否则进行下一步;
  7. 设栈内栈顶点的后面一个点为w;
  8. 如果w的dfs序大于等于lca的dfs序,进行下一步,否则跳转到步骤 10;
  9. 在 w 和 栈顶点 之间连边,并弹出栈顶,跳转到步骤7;
  10. 如果 lca 不是现在的栈顶点,在 lca 和现在栈顶点之间连边;
  11. 弹出栈顶,并将 lca 压入栈;
  12. 压入u,跳转到步骤3;

希望reader们都看懂了,可以把样例拿来自己推一推
这样一棵虚树就构造好了。
注意重置虚树的方法,不要 memset(我已经试过了)

「求解部分」

从根节点1开始,在虚树上DFS一遍,如果当前节点是叶子节点,就直接返回它的dp值,否则返回 min{它的dp值,它的所有儿子的返回值之和}
就相当于如果要割断 u 和 v ,要么割断它们的lca,要么分别将它们割断(花费加起来)。所以lca是有用的~


『源代码』

结合代码更容易理解~

/*Lucky_Glass*/ 
#include<bits/stdc++.h>
using namespace std;
const int N=250000;
typedef long long ll;
int QRead(){
    int a=0,b=1;char c=getchar();
    while(!('0'<=c && c<='9')) b=(c=='-'? -1:b),c=getchar();
    while('0'<=c && c<='9') a=(a<<3)+(a<<1)+c-'0',c=getchar();
    return a*b;
}
struct GRAPH{
    struct EDGE{
        int to,nxt,cst;
        EDGE(){}
        EDGE(int _to,int _nxt,int _cst):to(_to),nxt(_nxt),cst(_cst){}
    }edg[N*2+7];
    int adj[N+7],edgtot;
    void ReBuild(){
        memset(adj,-1,sizeof adj);
        edgtot=0;
    }
    void AddEdge(int u,int v,int cst,bool dir=false){
        edg[++edgtot]=EDGE(v,adj[u],cst);adj[u]=edgtot;
        if(!dir) edg[++edgtot]=EDGE(u,adj[v],cst),adj[v]=edgtot;
    }
}grp,tre;
int dfncnt,n,q,m,statop;
int dfn[N+7],fa[N+7][20],dep[N+7],pnt[N+7],sta[N+7];
ll dp[N+7];
void DFS(int u,int pre){
    fa[u][0]=pre;dep[u]=dep[pre]+1;dfn[u]=++dfncnt;
    for(int i=1;i<20;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
    for(int i=grp.adj[u];i!=-1;i=grp.edg[i].nxt){
        int v=grp.edg[i].to;
        if(v==pre) continue;
        dp[v]=min(dp[u],1ll*grp.edg[i].cst);
        DFS(v,u);
    }
}
int LCA(int u,int v){
    if(dep[u]>dep[v]) swap(u,v);
    for(int i=19;i>=0;i--)
        if(dep[fa[v][i]]>=dep[u])
            v=fa[v][i];
    if(u==v) return u;
    for(int i=19;i>=0;i--)
        if(fa[v][i]!=fa[u][i])
            v=fa[v][i],
            u=fa[u][i];
    return fa[u][0];
}
void Insert(int u){
    if(statop==1){sta[++statop]=u;return;}
    int lca=LCA(u,sta[statop]);
    if(lca==sta[statop]) return;
    while(statop>1 && dfn[sta[statop-1]]>=dfn[lca])
        tre.AddEdge(sta[statop-1],sta[statop],0,true),
        sta[statop--]=0;
    if(lca!=sta[statop]) tre.AddEdge(lca,sta[statop],0,true),sta[statop]=lca;
    sta[++statop]=u;
}
ll DP(int u){
    if(tre.adj[u]==-1) return dp[u];
    ll sum=0;
    for(int i=tre.adj[u];i!=-1;i=tre.edg[i].nxt)
        sum+=DP(tre.edg[i].to);
    tre.adj[u]=-1;
    return min(sum,dp[u]);
}
bool cmp(int a,int b){return dfn[a]<dfn[b];}
int main(){
    grp.ReBuild();
    tre.ReBuild();
    n=QRead();
    for(int i=1,u,v,cst;i<n;i++)
        u=QRead(),v=QRead(),cst=QRead(),
        grp.AddEdge(u,v,cst);
    dp[1]=(1ll<<60);
    DFS(1,0);
    q=QRead();
    while(q--){
        m=QRead();
        for(int i=0;i<m;i++) pnt[i]=QRead();
        sort(pnt,pnt+m,cmp);
        tre.edgtot=0;
        sta[statop=1]=1;
        for(int i=0;i<m;i++) Insert(pnt[i]);
        while(statop>1)
            tre.AddEdge(sta[statop-1],sta[statop],0,true),
            sta[statop--]=0;
        printf("%lld\n",DP(1));
    }
    return 0;
}

\(\mathcal{The\ End}\)

\(\mathcal{Thanks\ For\ Reading!}\)

没看懂的部分可以在我的邮箱(\(lucky\[email protected]\))里询问~

猜你喜欢

转载自www.cnblogs.com/LuckyGlass-blog/p/10122919.html