1. 程式人生 > >CH#56C 異象石(LCA)(DFS序)

CH#56C 異象石(LCA)(DFS序)

題目

Adera是Microsoft應用商店中的一款解謎遊戲。
異象石是進入Adera中異時空的引導物,在Adera的異時空中有一張地圖。這張地圖上有N個點,有N-1條雙向邊把它們連通起來。起初地圖上沒有任何異象石,在接下來的M個時刻中,每個時刻會發生以下三種類型的事件之一:
1. 地圖的某個點上出現了異象石(已經出現的不會再次出現);
2. 地圖某個點上的異象石被摧毀(不會摧毀沒有異象石的點);
3. 向玩家詢問使所有異象石所在的點連通的邊集的總長度最小是多少。
請你作為玩家回答這些問題。

題解

lca+dfs序

簡化題意後,這題就是一個動態求連線多點最小路徑長的問題。
對於這類問題,我們先跑一遍dfs,求出dfs序。對於一個新插入的點x,找到它dfs序相鄰的兩個出現異象石的節點,即為l和r。我們可以得到新增的路徑長為( -path(l,r)+path(l,x)+path(x,r) )/2。其實相當於在dfs序上處理這些問題,因為dfs序涉及入和出,所以才有除以2。再想,先除去path(l,r),然後加上(l,x)和(x,r),很好的表示了新的最小長度。刪除節點是大同小異,把這個點刪掉新增的路徑為( -(path(l,x)+path(x,r)) + path(l,r) )/2。
具體實現時,為了方便找到前驅和後繼,可以用set維護。

程式碼

#include<set>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int bin[30];

int n,m;
set<int> list;

struct E{int y,c,next;}e[maxn*2];int len=0,last[maxn];
void ins(int x,int y,int c)
{
    e[++len]=(E){y,c,last[x]};last[x]=len;
}

int id=0,dfn[maxn];
ll d[maxn];int dep[maxn],f[maxn][30];
void dfs(int x,int fa)
{
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(y==fa) continue;
        dfn[y]=++id;
        dep[dfn[y]]=dep[dfn[x]]+1;
        d[dfn[y]]=d[dfn[x]]+e[k].c;
        f[dfn[y]][0]=dfn[x];
        for(int i=1;i<=20;i++) f[dfn[y]][i]=f[f[dfn[y]][i-1]][i-1];
        dfs(y,x);//debug
    }
}
int lca(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=20;i>=0;i--)
        if(dep[x]-bin[i]>=dep[y]) x=f[x][i];
//      if(dep[x]-bin[i]<=dep[y]) x=f[x][i];debug
    if(x==y) return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

ll path(int x,int y)
{
    return d[x]+d[y]-2*d[lca(x,y)];//debug d[lca(x,y)]
}

char opt[4];
int main()
{
    bin[0]=1;for(int i=1;i<=20;i++) bin[i]=bin[i-1]<<1;
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y,c;
        scanf("%d%d%d",&x,&y,&c);
        ins(x,y,c);ins(y,x,c);
    }
    dfn[1]=++id;dfs(1,0);//dfn[1]=1;
    scanf("%d",&m);
    ll ans=0;
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%s",opt);
        if(opt[0]=='+')
        {
            scanf("%d",&x);
            if(list.size()==0) list.insert(dfn[x]);
            else if(list.size()==1)
            {
                ans=2*path(*list.begin(),dfn[x]);
                list.insert(dfn[x]);
            }
            else
            {
                list.insert(dfn[x]);
                set<int>::iterator il=list.find(dfn[x]),ir=il;
                if(ir==list.begin()) ir=list.end();ir--;//debug ir沒有-- 
                if(++il==list.end()) il=list.begin();
                ans=ans-path(*il,*ir)+path(*il,dfn[x])+path(dfn[x],*ir);
            }
        }
        else if(opt[0]=='-')
        {
            scanf("%d",&x);
            if(list.size()==1) list.clear();
            else if(list.size()==2)
            {
                ans=0;
                list.erase(dfn[x]);
            }
            else
            {
                set<int>::iterator il=list.find(dfn[x]),ir=il;
                if(ir==list.begin()) ir=list.end();ir--;//debug ir沒有-- 
                if(++il==list.end()) il=list.begin();
                ans=ans+path(*il,*ir)-path(*il,dfn[x])-path(dfn[x],*ir);
                list.erase(dfn[x]);
            }
        }
        else
        {
            printf("%lld\n",ans/2);
        }
    }
    return 0;
}