hdu 5296 Annoying problem (LCA)

題目大意:

給一棵帶權樹,和一個集合S,初始爲空。現有兩種操作:

1、選擇某個頂點加入S

2、將S中某個頂點去掉。

問:每次操作後,使得集合S中的點聯通的最小邊權和是多少。



一、思考問題的本質是什麼?

要使邊權和最小,也即每次增加的邊權要求最小。

當添加第一個頂點的時候,增加的邊權爲0

添加第二個頂點的時候,增加的邊權爲該點到第一個頂點的路徑長度。

添加第三個頂點的時候,增加的最小邊權應當是該點到第一個頂點和第二個頂點形成的鏈的距離。(當然,若在鏈上,距離爲0)

……

就是說,添加頂點u的時候,之前添加的頂點已經形成了若干條鏈(任意兩個頂點構成一條)

此時要找的就是u到這些鏈的最短的距離是多少,也就是:在之前添加的頂點中(集合S中)找兩個頂點x,y,使得u到x、y構成的鏈的距離最短


二、如何來找這兩個點?

觀察一棵樹中頂點的DFS序,發現有如下特點:

1、對於任意一個結點u,DFS序比它大的結點,由u的子孫和u的右側的所有節點(不含根節點)構成。

2、對於任意一個結點u,DFS序比它小的結點,由u到根節點經過的所有結點(不含u本身,其實就是u的所有祖先啦)和u的左側的所有節點構成。

這也就意味着,可以通過DFS序,來確定樹中兩點的相對位置


對於添加的點u:

1、若集合S中所有點的DFS序都比它小,那麼集合中所有點都位於u的左側或者是u的某個祖先結點。若存在某個點是u的祖先,爲了使得增加的距離最少,那麼找距離u最近的那個,即DFS序最大的那個,設爲x,再任意選一個頂點y,求u到x-y鏈的距離即可;若均位於u的左側,此時又有兩種情況,一種是所有頂點都在一棵子樹中,此時找DFS序最小的那個點(因爲最靠近u)作爲x,再任選一個作爲y,再一種是頂點分散在u的左側的不同子樹中,那麼選取DFS序最小的和最大的作爲x、y(最小的和最大的必然位於不同子樹中),求u到鏈x-y的距離即可。綜上,爲了統一方便,分別找集合S中DFS序最小和最大的兩個點作爲x、y即可。

2、若集合S中所有點的DFS序都比它大,那麼集合中所有點都位於u的右側或者是u的子孫,同上分情況考慮,可以得到:也是找集合S中DFS序最小和最大的兩個點作爲x、y

3、若集合S中的點的DFS序有比u大的,也有比u小的,此時分如下幾種情況:

         (1)比頂點u的DFS序大的點都在u的右側,比頂點u的DFS序小的點都在u的左側。此時任意選取左側一個點和右側一個點作爲x、y即可。

         (2)存在u的子孫在集合S中,且比頂點u的DFS序小的點都在u的左側。容易知道,此時無需再增加邊。即選取的時候只需保證鏈x-y經過u即可。

         (3)比頂點u的DFS序大的點都在u的右側,且存在u的祖先結點在S中。此時選取u的祖先結點DFS序最大的那個(也就是DFS序比u小的最大的那個),然後在右側隨便選一個即可。

         (4)存在u的子孫在集合S中,且存在u的祖先結點在S中,顯然此時也無需再增加邊,選取的時候保證鏈x-y經過u即可。

         綜上,爲了統一方便,分別找集合S中DFS序比u小的最大的那個、比u大的任意一個即可。


三、找到了之後如何求點到鏈的距離?

設dis[u]表示頂點u到根節點的距離

那麼u點 到x點 距離表示爲 dis[u]+dis[x]-2dis[lca(x,u)]
那麼u點 到y點 距離表示爲 dis[u]+dis[y]-2dis[lca(y,u)]
上面的距離的和減掉 x到y的距離dis[x]+dis[y]-2dis[lca(y,x)]

減去之後,發現得到的結果剛好是u到鏈x-y距離的2倍,除以2就是所求的結果了。


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<stack>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
using namespace std;
#define maxn 100001
#define maxq 70001
struct Edge{
    int to,next,w;
}edge[maxn<<1];

int dis[maxn],head[maxn],stk[maxn<<1],dep[maxn<<1],cnt,pos[maxn],E[maxn<<1],DFN[maxn],dfn,f[maxn<<1][20],dfn2,D[maxn];
bool vis[maxn];

inline void add(int u,int v,int w)
{
    edge[cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

void init()
{
    memset(head,-1,sizeof(head));
    memset(pos,-1,sizeof(pos));
    memset(vis,0,sizeof(vis));
    cnt=dfn=dfn2=0;
}

void dfs(int u,int deep)
{
    if(pos[u]!=-1) return;
    DFN[dfn2]=u,D[u]=dfn2++,E[dfn]=u,dep[dfn]=deep,pos[u]=dfn++;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(pos[v]==-1)
        {
            dis[v]=dis[u]+edge[i].w;
            dfs(v,deep+1);
            E[dfn]=u,dep[dfn++]=deep;
        }
    }
}

void init_RMQ(int n)
{
    for(int i=1;i<=n;++i) f[i][0]=i;
    for(int j=1;(1<<j)<=n;++j)
        for(int i=1;i+(1<<j)-1<=n;++i)
        {
            if(dep[f[i][j-1]]<dep[f[i+(1<<(j-1))][j-1]]) f[i][j]=f[i][j-1];
            else f[i][j]=f[i+(1<<(j-1))][j-1];
        }
}

inline int RMQ(int L,int R)
{
    int k=0;
    while(1<<(k+1)<=R-L+1) ++k;
    if(dep[f[L][k]]<dep[f[R-(1<<k)+1][k]]) return f[L][k];
    return f[R-(1<<k)+1][k];
}

inline int lca(int u,int v)
{
    if(pos[u]>pos[v]) return E[RMQ(pos[v],pos[u])];
    return E[RMQ(pos[u],pos[v])];
}

set<int> S;
set<int>::iterator it;

int cal(int u)
{
    int x,y;
    if(S.size()<1) return 0;
    it=S.lower_bound(D[u]);
    if(it==S.begin()||it==S.end())
    {
        x=DFN[*S.begin()];
        y=DFN[*S.rbegin()];
    }
    else{
        x=DFN[*it];
        --it;
        y=DFN[*it];
    }
    return dis[u]+dis[lca(x,y)]-dis[lca(x,u)]-dis[lca(y,u)];

}

int main()
{
    int T,n,i,u,v,w,q,k;
    scanf("%d",&T);
    for(int ca=1;ca<=T;++ca)
    {
        scanf("%d%d",&n,&q);
        init();
        S.clear();
        for(i=1;i<n;++i)
        {
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
        }
        dis[1]=0;
        dfs(1,0);
        init_RMQ(2*n-1);
        printf("Case #%d:\n",ca);

        int ans=0;
        while(q--){
            scanf("%d%d",&k,&u);
            if(k==1)
            {
                if(!vis[u])
                {
                    vis[u]=1;
                    ans+=cal(u);
                    S.insert(D[u]);
                }
            }
            else
            {
                if(vis[u])
                {
                    vis[u]=0;
                    S.erase(D[u]);
                    ans-=cal(u);
                }
            }
            printf("%d\n",ans);
        }

    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章