1. 程式人生 > >學習筆記 - 虛樹

學習筆記 - 虛樹

虛樹 - 學習筆記

初學的時候整個人是懵的
不過總算是弄懂了


『演算法概述』

對於一類樹上的問題,如果僅有一部分點對答案“有用”(也就是說另一部分“可以不要”),那麼我們考慮只儲存那些有用的點。這就是虛樹的思想。所以為什麼它叫虛樹?我也不知道……
通常情況下我們把一些點的 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]\))裡詢問~