2125: 最短路
Time Limit: 1 Sec Memory Limit: 259 MB
Submit: 1873 Solved: 754
[Submit][Status][Discuss]
Description
给一个N个点M条边的连通无向图,满足每条边最多属于一个环,有Q组询问,每次询问两点之间的最短路径。
Input
输入的第一行包含三个整数,分别表示N和M和Q 下接M行,每行三个整数v,u,w表示一条无向边v-u,长度为w 最后Q行,每行两个整数v,u表示一组询问
Output
输出Q行,每行一个整数表示询问的答案
Sample Input
9 10 2
1 2 1
1 4 1
3 4 1
2 3 1
3 7 1
7 8 2
7 9 2
1 5 3
1 6 4
5 6 1
1 9
5 7
Sample Output
5
6
什么是圆方树:
对于一棵仙人掌
- 仙人掌的所有点 → 圆方树中的所有圆点
- 仙人掌的所有不在环中的边 → 连接圆方树中两个圆点的边
- 仙人掌中的其中一个环 → 圆方树中的其中一个方点, 这个方点向当前环中的所有圆点连边,边的长度 = 这个点到这个环中最接近根的那个点的距离
一张非常形象的图,来自于一个课件
其实看了这个图应该就什么都懂了,一些性质也很容易被发现
仙人掌两点的最短路:
先建立出圆方树,关于如何建立圆方树:双联通分量
之后将问题转化成圆方树的LCA,LCA可以用树链剖分解决
考虑求LCA的两种情况:
- 如果两点的LCA是圆点:可以证明仙人掌树上的最短路 = 圆方树上的最短路
- 如果两点的LCA是方点:说明这两点会在环上相遇,先求出这两点到环的距离,然后在环上分类讨论下就可以了
搞定
剩下的代码中有注释
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
typedef struct Road
{
int v;
int len;
}Road;
Road now;
vector<Road> G[20005], T[20005];
int n, t, cnt, Time[20005], low[20005], tp[20005], dep[20005], fa[20005], a[20005], dis[20005];
struct RST
{
int fa[20005], size[20005], hson[20005], top[20005], dep[20005], dis[20005];
int t, rak[20005], id[20005], cir[20005], zn[20005];
void Sech1(int u, int p) //对树先来一套基本操作,顺便求出每个节点的重儿子
{
int v, i;
fa[u] = p, dep[u] = dep[p]+1;
size[u] = 1;
for(i=0;i<T[u].size();i++)
{
v = T[u][i].v;
if(v==p)
continue;
dis[v] = dis[u]+T[u][i].len;
Sech1(v, u);
size[u] += size[v];
if(size[v]>size[hson[u]])
hson[u] = v;
}
}
void Sech2(int u, int p) //重链剖分
{
int i, v;
top[u] = p;
rak[u] = ++t, id[t] = u;
if(hson[u])
Sech2(hson[u], p);
for(i=0;i<T[u].size();i++)
{
v = T[u][i].v;
if(v==fa[u] || v==hson[u])
continue;
Sech2(v, v);
}
}
int LCA(int u, int v)
{
while(top[u]^top[v])
{
if(dep[top[u]]<dep[top[v]])
v = fa[top[v]];
else
u = fa[top[u]];
}
if(dep[u]<dep[v])
return u;
return v;
}
int Jump(int u, int lca) //跳到lca的那个环上
{
int ret;
while(top[u]!=top[lca])
ret = top[u], u = fa[top[u]];
if(u==lca) //如果lca正好是一条链的尾端,那么上一个链的链头肯定就是当前要求的点
return ret;
return id[rak[lca]+1]; //如果lca不在链的尾端,那么当前要求的点一定是lca的重儿子!要不怎么会在一条链上
}
int Query(int u, int v)
{
int lca, A, B, d1, d2;
lca = LCA(u, v);
if(lca<=n) //如果LCA是圆点,说明他们不在环上相遇,直接返回它们与lca的距离即可
return dis[u]+dis[v]-2*dis[lca];
else //在环上相遇
{
A = Jump(u, lca), B = Jump(v, lca);
d1 = dis[A]-dis[lca], d2 = dis[B]-dis[lca];
if(zn[A]==0)
d1 = cir[lca]-d1;
if(zn[B]==0)
d2 = cir[lca]-d2; //d1和d2分别表示A和B离当前环中最接近根的那个点的距离
return dis[u]-dis[A]+dis[v]-dis[B]+min(abs(d1-d2),cir[lca]-abs(d1-d2));
}
}
}RST;
void CreateS(int u, int v, int len)
{
int R, sum, i, D;
D = R = 0, sum = len, cnt++; //cnt为当前新建的方点
for(i=v;1;i=fa[i])
{
a[++R] = i;
if(i==u)
break;
sum += dis[i]-dis[fa[i]];
}
for(i=1;i<=R/2;i++)
swap(a[i], a[R-i+1]);
RST.cir[cnt] = sum;
for(i=1;i<=R;i++)
{
now.len = min(D, sum-D);
now.v = a[i], T[cnt].push_back(now);
now.v = cnt, T[a[i]].push_back(now);
RST.zn[a[i]] = (now.len==D);
D += dis[a[i+1]]-dis[a[i]];
}
}
void Tarjan(int u, int p)
{
int i, v;
Time[u] = low[u] = ++t;
fa[u] = p, dep[u] = dep[p]+1;
for(i=0;i<G[u].size();i++)
{
v = G[u][i].v;
if(v==p)
continue;
if(Time[v]==0)
{
dis[v] = dis[u]+G[u][i].len;
Tarjan(v, u);
low[u] = min(low[u], low[v]);
}
else
low[u] = min(low[u], Time[v]);
if(low[v]>Time[u])
{
T[u].push_back(G[u][i]);
now.v = u, now.len = G[u][i].len;
T[v].push_back(now);
}
}
for(i=0;i<G[u].size();i++)
{
v = G[u][i].v;
if(fa[v]!=u && Time[v]>Time[u])
CreateS(u, v, G[u][i].len);
}
}
int main(void)
{
int i, m, T, x, y;
scanf("%d%d%d", &n, &m, &T), cnt = n;
for(i=1;i<=m;++i)
{
scanf("%d%d%d", &x, &y, &now.len);
now.v = y, G[x].push_back(now);
now.v = x, G[y].push_back(now);
}
Tarjan(1, 0);
RST.Sech1(1, 0);
RST.Sech2(1, 1);
while(T--)
{
scanf("%d%d", &x, &y);
printf("%d\n", RST.Query(x, y));
}
return 0;
}
/*
15 18 0
10 15 15 9 9 14 14 2
10 13 13 12 12 11 11 10 10 9 9 2
3 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1
*/