虛樹詳解+例子分析+模板


概念:

給出一棵樹.

每次詢問選擇一些點,求一些東西.這些東西的特點是,許多未選擇的點可以通過某種方式剔除而不影響最終結果.

於是就有了建虛樹這個技巧.....

我們可以用log級別的時間求出點對間的lca....

那么,對於每個詢問我們根據原樹的信息重新建樹,這棵樹中要盡量少地包含未選擇節點. 這棵樹就叫做虛樹.

接下來所說的"樹"均指虛樹,原來那棵樹叫做"原樹".

構建過程如下:

按照原樹的dfs序號(記為dfn)遞增順序遍歷選擇的節點. 每次遍歷節點都把這個節點插到樹上.

首先虛樹一定要有一個根. 隨便扯一個不會成為詢問點的點作根.(並不覺得是這樣)

維護一個棧,它表示在我們已經(用之前的那些點)構建完畢的虛樹上,以最后一個插入的點為端點的DFS鏈.

設最后插入的點為p(就是棧頂的點),當前遍歷到的點為x.我們想把x插入到我們已經構建的樹上去.

求出lca(p,x),記為lca.有兩種情況:

  1.p和x分立在lca的兩棵子樹下.

  2.lca是p.

  (為什么lca不能是x?

   因為如果lca是x,說明dfn(lca)=dfn(x)<dfn(a),而我們是按照dfs序號遍歷的,於是dfn(a)<dfn(x),矛盾.)

 對於第二種情況,直接在棧中插入節點x即可,不要連接任何邊(后面會說為什么).

對於第一種情況,要仔細分析.

我們是按照dfs序號遍歷的(因為很重要所以多說幾遍......),有dfn(x)>dfn(p)>dfn(lca).

這說明什么呢? 說明一件很重要的事:我們已經把lca所引領的子樹中,p所在的子樹全部遍歷完了!

  簡略的證明:如果沒有遍歷完,那么肯定有一個未加入的點h,滿足dfn(h)<dfn(x),

        我們按照dfs序號遞增順序遍歷的話,應該把h加進來了才能考慮x.

這樣,我們就直接構建lca引領的,p所在的那個子樹. 我們在退棧的時候構建子樹.

p所在的子樹如果還有其它部分,它一定在之前就構建好了(所有退棧的點都已經被正確地連入樹中了),就剩那條鏈.

如何正確地把p到lca那部分連進去呢?

設棧頂的節點為p,棧頂第二個節點為q.

重復以下操作:

  如果dfn(q)>dfn(lca),可以直接連邊q->p,然后退一次棧.

  如果dfn(q)=dfn(lca),說明q=lca,直接連邊lca->p,此時子樹已經構建完畢.

  如果dfn(q)<dfn(lca),說明lca被p與q夾在中間,此時連邊lca->q,退一次棧,再把lca壓入棧.此時子樹構建完畢.

    如果不理解這樣操作的緣由可以畫畫圖.....

最后,為了維護dfs鏈,要把x壓入棧. 整個過程就是這樣.....

下圖是自己畫的一個例子:

附上模板:

///////////--虛樹模板--///////////////////
//傳入樹的一個子集,若以按dfs序排好直接調用build_vtree
//否則調用vsort
//復雜度O( nlog(n) ) n是虛樹的大小

#define N 11000
#define LN 20

////////////--標准建鄰接表--/////////////
struct node
{
    int to,next;
}edge[2*N];

int cnt,pre[N];


void add_edge(int u,int v)
{
    edge[cnt].to = v;
    edge[cnt].next = pre[u];
    pre[u] = cnt++;
}
//////////////////////////////////////

int deep[N];//記錄每個點的深度
int order[N];//記錄每個點的訪問次序
int indx=0;

struct Lca_Online
{
    int _n;
    
    int dp[N][LN];
    
    void _dfs(int s,int fa,int dd)
    {
        deep[s] = dd;
        order[s] = ++indx;
        for(int p=pre[s];p!=-1;p=edge[p].next)
        {
            int v = edge[p].to;
            if(v == fa) continue;
            _dfs(v,s,dd+1);
            dp[v][0] = s;
        }
    }
    
    void _init()
    {
        for(int j=1;(1<<j)<=_n;j++)
        {
            for(int i=1;i<=_n;i++)
            {
                if(dp[i][j-1]!=-1) dp[i][j] = dp[ dp[i][j-1] ][j-1];
            }
        }
    }
    void lca_init(int n)
    {
        _n = n;
        memset(dp,-1,sizeof(dp));
        //_dfs(firstid,-1,0);
        indx = 0;
        _dfs(1,-1,0);
        _init();
    }
    
    int lca_query(int a,int b)
    {
        if(deep[a]>deep[b]) swap(a,b);
        //調整b到a的同一高度
        for(int i=LN-1;deep[b]>deep[a];i--)
            if(deep[b]-(1<<i) >= deep[a]) b = dp[b][i];
        if(a == b) return a;
        for(int i=LN-1;i>=0;i--)
        {
            if(dp[a][i]!=dp[b][i]) a = dp[a][i],b = dp[b][i];
        }
        return dp[a][0];
    }
}lca;

int stk[N],top;
int mark[N];//標示虛樹上的點是否是無用點
vector<int>tree[N];//存邊
vector<int>treew[N];//存權

void tree_add(int u,int v,int w)
{
    tree[u].push_back(v);
    tree[v].push_back(u);
    treew[u].push_back(w);
    treew[v].push_back(w);
}

//使用前調用 lca.lca_init(n); 初始化
//返回虛樹根節點,虛樹的邊默認為原樹上兩點的距離
int build_vtree(int vp[],int vn)//傳入按dfs序數組,以及長度(要自己寫按dfs排序的數組)
{
    if(vn == 0) return -1;
    top = 0;
    
    stk[top++] = vp[0];
    tree[ vp[0] ].clear();
    treew[ vp[0] ].clear();
    mark[ vp[0] ]=1;
    for(int i=1;i<vn;i++)
    {
        int v = vp[i];
        
        int plca = lca.lca_query(stk[top-1], v);//最近公共祖先
        if(plca == stk[top-1]) ;//不處理
        else
        {
            int pos=top-1;
            while(pos>=0 && deep[ stk[pos] ]>deep[plca])
                pos--;
            pos++;
            for(int j=pos;j<top-1;j++)
            {
                tree_add(stk[j],stk[j+1],deep[stk[j+1]]-deep[stk[j]]);
            }
            int prepos = stk[pos];
            if(pos == 0)
            {
                tree[plca].clear(),treew[plca].clear(),stk[0]=plca,top=1;
                mark[plca] = 0;
            }
            else if(stk[pos-1] != plca)
            {
                tree[plca].clear(),treew[plca].clear(),stk[pos]=plca,top=pos+1;
                mark[plca] = 0;
            }
            else top = pos;
            tree_add(prepos,plca,deep[prepos]-deep[plca]);
            
        }
        tree[v].clear();
        treew[v].clear();
        stk[top++] = v;
        mark[v] = 1;
    }
    for(int i=0;i<top-1;i++)
    {
        tree_add(stk[i], stk[i+1], deep[stk[i+1]]-deep[stk[i]]);
    }

    return vp[0];
}

//////////////--先排序,再建虛樹--//////////////////////
struct vnode
{
    int order,id;
}vg[N];
int vcmp(vnode t1,vnode t2)
{
    return t1.order<t2.order;
}
int vsort(int vp[],int vn)//傳入未排序的數組,以及長度.
{
    for(int i=0;i<vn;i++) vg[i].id = vp[i],vg[i].order = order[ vp[i] ];
    sort(vg,vg+vn,vcmp);
    for(int i=0;i<vn;i++) vp[i]=vg[i].id;
    
    return build_vtree(vp, vn);
}
////////////////////////////////////

//void dfs(int s,int fa)
//{
//    printf("%d ",s);
//    for(int i=0;i<tree[s].size();i++)
//    {
//        int v = tree[s][i];
//        if(v == fa) continue;
//        dfs(v,s);
//    }
//}
//
//int main()
//{
//    int n;
//    cin>>n;
//    cnt = 0;
//    memset(pre,-1,sizeof(pre));
//    for(int i=1;i<n;i++)
//    {
//        int a,b;
//        cin>>a>>b;
//        add_edge(a, b);
//        add_edge(b, a);
//    }
//    int m;
//    cin>>m;
//    int save[100];
//    for(int i=0;i<m;i++) scanf("%d",save+i);
//    lca.lca_init(n);
//    int root = vsort(save, m);
//    if(root != -1)
//    {
//        dfs(root,-1);
//    }
//    return 0;
//}
/*
 7
 1 3
 1 2
 2 6
 3 5
 3 4
 4 7
 3
 6 7 5
*/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM