2125: 最短路
Time Limit: 1 Sec Memory Limit: 259 MB
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
HINT
对于100%的数据,N<=10000,Q<=10000
思路:
每个点有两个值,一个是从根到这个点的最短路d[i],一个是从根沿dfs树到这个点的距离rd[i].
之后是一个很牛逼的建图,把环上的点都连到环中深度最浅的点得到一颗树,并维护每个点所在的环以及每个环的环长。
对于一个询问(x,y),假设dep[x]>dep[y],分情况:
1.如果x和y的lca是y,那么答案为d[x]-d[y].
2.如果x和y的lca没在环里,那么答案为d[x]+d[y]-2*d[lca].
3.如果x和y的lca在环里,设x,y向上走遇到的第一个环上节点分别是a,b,那么答案为d[x]-d[a]+d[y]-d[b]+min(abs(rd[a]-rd[b]),环长-abs(rd[a]-rd[b])).
不知道什么仙人掌,乱搞吧。。。
方法就像上面大佬的题解一样,辣基来解释一下。。。
我们显然可以建出一棵树,然后还有一些非树边。
考虑到由于有这些非树边,我们的选择就可能很混乱,往上往下,跳与不跳都有可能,于是我们考虑统一一下方向,让我们的操作更有规律。所以我们把环上的点都连到环中深度最浅的点得到一颗树,并维护每个点所在的环以及每个环的环长。就是代码里rebuild的部分(虽然改了图,但是保存了原图的环信息,总权值wgh,从标志点(环中最浅)单方向到每个点的环长rd等等)
之后对于深度较深的一个点u就只能向上走了。因为如果向下走不能跳出u的子树的话,显然是不优的。而如果可以跳出,就说明u一定在一个环中,那么它在新树中也是不会向下走的。不走环就走fa,走环就走兄弟节点。
我们提前用spfa维护了原图上的最短路d,我们讨论x,y两个点在新树上的位置关系。
假设dep[x]>dep[y](1为root)
1.如果x和y的lca是y,那么答案为d[x]-d[y].
因为x只能到兄弟或者父亲,所以x到1的路径上(不管哪条)都会经过y。
2.如果x和y的lca没在环里,那么答案为d[x]+d[y]-2*d[lca].
因为在x,y走向lca的过程中,这两个点一定不会在同一个环中。(否则lca也一定在环中)也就是说不管怎么走,x和y之间的所有路径一定会经过lca。所以ans就是x到lca的d加上y到lca的d。
3.如果x和y的lca在环里,设x,y向上走遇到的第一个环上节点分别是a,b,那么答案为d[x]-d[a]+d[y]-d[b]+min(abs(rd[a]-rd[b]),环长-abs(rd[a]-rd[b])).
与上一种情况相反(到a,b之前是一样的),到a,b之后我们就有两条路径可以选择了(环上)。
这种建树方法的好处第一就是破环,方便我们判断两点在原图上的位置关系。第二就是优化上述第3种情况,因为同一个环上的点dep是一样的,所以可以直接倍增的向上跳找到a和b。
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define N 50010
#define M 100010
using namespace std;
const int P = 13;
int n, m, q, idc=1, idx, cnt;
int head[N], del[M], dfn[N], st[N], dis[N], vis[N], rd[N], dep[N], place[N], wgh[N], acc[N][P+1];
inline int read(){
int x = 0, f = 1; char ch = getchar();
while ( ch < '0' || ch > '9' ) { if(ch == '-') f = -1; ch = getchar(); }
while ( ch >= '0' && ch <= '9' ) { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
struct Edge{
int to, nxt, w;
}ed[M << 1];
inline void adde(int u, int vis, int w){
ed[++idc].to = vis;
ed[idc].nxt = head[u];
ed[idc].w = w;
head[u] = idc;
}
void spfa() {
memset(dis, 0x3f, sizeof(dis) );
queue<int> q; q.push(1), dis[1] = 0;
while ( !q.empty() ) {
int u = q.front(); q.pop();
vis[u] = 0;
for(int i=head[u]; i; i=ed[i].nxt) {
int v = ed[i].to;
if(dis[v] > dis[u] + ed[i].w) {
dis[v] = dis[u] + ed[i].w;
if( !vis[v] ) {
vis[v] = 1;
q.push( v );
}
}
}
}
}
void rebuild(int x, int y) {//建图
if(x == y) return;
place[x] = cnt;
adde(y, x, 0); adde(x, y, 0);//
del[st[x]] = del[st[x]^1] = 1;
wgh[cnt] += ed[st[x]].w;
rebuild(ed[st[x]^1].to, y);
}
void dfs(int u) {
dfn[u] = ++idx;
for(int i=head[u]; i; i=ed[i].nxt) {
int v = ed[i].to;
if(i!=(st[u]^1) && i<=idc) {
if( !dfn[v] ) {
rd[v] = rd[u] + ed[i].w;
st[v] = i;
dfs( v );
}
else if(dfn[v] < dfn[u]) {
wgh[++cnt] = ed[i].w;//环的总权值
rebuild(u, v);
}
}
}
}
void dfs2(int u, int f) {
acc[u][0] = f;
for(int i=1; i<=P; i++)
acc[u][i] = acc[acc[u][i-1]][i-1];
for(int i=head[u]; i; i=ed[i].nxt) {
int v = ed[i].to;
if(!del[i] && !dep[v]) {
dep[v] = dep[u] + 1;
dfs2(v, u);
}
}
}
int query(int u, int v) {
if(dep[u] < dep[v]) swap(u, v);
int a = u , b = v;
for(int i=P; i>=0; i--) if(dep[acc[u][i]] >= dep[v]) u = acc[u][i];
if(u == v) return dis[a] - dis[b];
for(int i=P; i>=0; i--) if(acc[u][i] ^ acc[v][i]) u = acc[u][i], v = acc[v][i];
if(place[u] && place[u] == place[v]) {
int S = abs(rd[u] - rd[v]);
return dis[a] - dis[u] + dis[b] - dis[v] + min(S, wgh[place[u]]-S);
}
return dis[a] + dis[b] - 2 * dis[acc[u][0]];
}
int main() {
scanf("%d%d%d", &n, &m, &q);
for(register int i=1; i<=m; i++){
int u = read(), v = read(), w = read();
adde(u, v, w); adde(v, u, w);
}
spfa(), dfs( 1 ), dep[1] = 1, dfs2(1, 1);
while ( q-- ) {
int u = read(), v = read();
printf("%d\n", query(u, v));
}
return 0;
}