BZOJ 2125 最短路:圆方树+LCA(倍增)

题意:给出一个仙人掌,多次询问两点间最短距离。

题解:

树的情况下,只需要LCA即可,树剖或者倍增都行,树剖常数比倍增小一些,而且确实好写。
仙人掌情况下,只需要建立圆方树,显然方点到圆点父亲的边权应为0。讨论两点LCA的性质:
LCA为圆点:这种情况说明此圆点确为两点LCA,答案就是dis(u)+dis(v)-2*dis(lca)
LCA为方点:这种情况下说明,u和v两点向上寻找LCA的时候,在环上相遇。这时候我们要讨论环上的最短路径,也就是经过环根与不经过环根两段弧。也就是说我们需要直到u和v分布在LCA的哪两个儿子下边。

由于个人比较懒,且不知道树剖方式如何能够优美的求出u和v分布在LCA的哪两个儿子下边……于是采用了天生可以求出这个东西的倍增LCA。同时为了边权容易处理,使用的也是倍增方式统计路径长,导致常数比本来就比树剖常数大的倍增常数又大了一倍,大约跑了450ms(时限1000ms)

至于这个如何能快速的求出环上的两段弧,有一个很trick的方法,即记录该点到环根的最短路径走的是左边一段还是右边一段,然后环上两点u,v,如果他们在同一侧,把该最短路径直接相减即得到一段弧,否则,把最短路径相加即得到一段弧。

Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e4+100;
vector<pair<int,int> > E[maxn],ET[maxn];
bool inCircle[maxn];
int dfn[maxn],dfs_clock;
int dis[maxn];
int n,m,q,N;
int fa[maxn],dep[maxn],len[maxn];
int st[maxn][16];
int l[maxn][16];
int flag[maxn];
inline void addEdge(int x,int y,int w){
    E[x].push_back(make_pair(y,w));
}
inline void addEdgeT(int x,int y,int w){
    ET[x].push_back(make_pair(y,w));
}
void input(){
    scanf("%d%d%d",&n,&m,&q);
    N = n;
    for (int i=0;i<m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addEdge(u,v,w);
        addEdge(v,u,w);
    }
}
void tarjan(int x){
    dfn[x] = ++ dfs_clock;
    for (int i=0;i<E[x].size();i++){
        int v = E[x][i].first;
        int w = E[x][i].second;
        if (v==fa[x])continue;
        if (!dfn[v]){
            fa[v] = x;
            dis[v] = dis[x]+w;
            tarjan(v);
        }else if (dfn[v]<dfn[x]){
            n++;
            len[n] = dis[x]-dis[v]+w;
            fa[n] = v;
            addEdgeT(v,n,0);
            int temp = x;
            int cnt=0;
            while (temp!=v){
                inCircle[temp] = 1;
                int w1 = dis[temp]-dis[v];
                int w2 = len[n]-w1;
                int wm = min(w1,w2);
                addEdgeT(n,temp,wm);
                flag[temp] = wm==w2;
                temp = fa[temp];
            }
        }
    }
    dfs_clock--;
    if (!inCircle[x]){
        addEdgeT(fa[x],x,dis[x]-dis[fa[x]]);
    }
}
void dfs(int x){
    for (int i=1;i<=15&&st[x][i-1];i++){
        st[x][i] = st[st[x][i-1]][i-1];
        l[x][i] = l[x][i-1]+l[st[x][i-1]][i-1];
    }
    for (int i=0;i<ET[x].size();i++){
        int v = ET[x][i].first;
        int w = ET[x][i].second;
        if (v==st[x][0])continue;
        st[v][0] =x;
        dep[v] = dep[x]+1;
        l[v][0] = w;
        dfs(v);
    }
}
inline int calc(int root,int son1,int son2){
    if (flag[son1]==flag[son2]){
        return abs(l[son1][0]-l[son2][0]);
    }else{
        return len[root]-l[son1][0]-l[son2][0];
    }
}
int query(int x,int y){
    if (x==y){
        return 0;
    }
    if (dep[x]<dep[y])swap(x,y);
    int ans =0;
    for (int i=15;i>=0&&dep[x]!=dep[y];i--){
        if (dep[st[x][i]]>=dep[y])ans+=l[x][i],x = st[x][i];
    }
    if (x==y)return ans;
    for (int i=15;i>=0&&st[x][0]!=st[y][0];i--){
        if (st[x][i]!=st[y][i]){
            ans+=l[x][i]+l[y][i];
            x = st[x][i];
            y = st[y][i];
        }
    }
    int lca = st[x][0];
    if (lca>N){
        ans+=min(l[x][0]+l[y][0],calc(lca,x,y));
    }else{
        ans += l[x][0]+l[y][0];
    }
    return ans;
}
void solve(){
    while (q--){
        int u,v;
        scanf("%d%d",&u,&v);
        printf("%d\n",query(u,v));
    }
}
signed main(){
    input();
    tarjan(1);
    dep[1]=1;
    dfs(1);
    solve();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/calabash_boy/article/details/80011536