题意:给出一个仙人掌,多次询问两点间最短距离。
题解:
树的情况
下,只需要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;
}