题目大意
给一个N个点M条边的连通无向图,满足每条边最多属于一个环,有Q组询问,每次询问两点之间的最短路径。N<=10000,Q<=10000。
分析
这道图的题目可以思考如何用树上的做法来解决这道题。在简洁的题目里得出了一个极为重要的信息每个点只会出现在一个环中。首先当然是先跑一遍Dij,处理出从编号为1的点到各个点的距离,然后我们不妨先处理出每个环,并标上号,并处理出环上的边权和(有一些细节需谨慎处理),然后思考对于每组询问,分为如下三种情况处理即可。
- 若 a a a与 b b b在一条链上则答案显然为 a b s ( d i s [ a ] − d i s [ b ] ) abs(dis[a] - dis[b]) abs(dis[a]−dis[b])
- 若
a
a
a与
b
b
b的
L
C
A
LCA
LCA相对的那两个儿子节点在同一个环中那么答案为
d i s [ u ] + d i s [ v ] − d i s [ a ] − d i s [ b ] + m i n ( a b s ( s u m [ a ] − s u m [ b ] ) , t o t l e n [ c o l [ a ] ] − a b s ( s u m [ a ] − s u m [ b ] ) ) dis[u] + dis[v] - dis[a] - dis[b] + min(abs(sum[a] - sum[b]),totlen[col[a]] - abs(sum[a] - sum[b])) dis[u]+dis[v]−dis[a]−dis[b]+min(abs(sum[a]−sum[b]),totlen[col[a]]−abs(sum[a]−sum[b]))
其中 u u u, v v v分别为 a a a与 b b b L C A LCA LCA的相对的那个儿子节点, s u m [ a ] sum[a] sum[a]为从环上钦定的节点到 a a a的距离,所以 s u m [ a ] − s u m [ b ] sum[a] - sum[b] sum[a]−sum[b]即为环上两点间的距离, t o t l e n [ a ] totlen[a] totlen[a]表示编号为 a a a的环的边权和, c o l [ a ] col[a] col[a]表示 a a a号点所在的环的编号。那么这个答案还是比较显然的 m i n ( a b s ( s u m [ a ] − s u m [ b ] ) , t o t l e n [ c o l [ a ] ] − a b s ( s u m [ a ] − s u m [ b ] ) ) min(abs(sum[a] - sum[b]),totlen[col[a]] - abs(sum[a] - sum[b])) min(abs(sum[a]−sum[b]),totlen[col[a]]−abs(sum[a]−sum[b]))就是比较在环上从 a a a走到 b b b是按照原顺序比较近还是按照另一个方向走过去比较近。 - 若
a
a
a与
b
b
b的
L
C
A
LCA
LCA相对的那两个儿子节点不在同一个环中那么答案为
d i s [ u ] + d i s [ v ] − 2 ∗ d i s [ o p t [ a ] [ 0 ] ] dis[u] + dis[v] - 2 * dis[opt[a][0]] dis[u]+dis[v]−2∗dis[opt[a][0]]
其中 o p t [ a ] [ 0 ] opt[a][0] opt[a][0]是 a a a往上跳20步所到的点,在此处即为 u u u与 v v v点的 L C A LCA LCA。 - 如果口糊得不太清楚可以自己画一下图挺好理解的。
Code
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 1e4 + 10;
const int M = 6e5 + 10;
struct Edge{
int to,next,val;
} f[M << 1];
struct Node{
int dis,u;
};
int Q,n,m,cnt,head[N],dep[N],opt[N][16],dfn[N],totlen[N],tot,col[N];
ll dis[N],sum[N];
bool vis[N];
vector<int> son[N];
priority_queue<Node> q;
int read()
{
int x = 0;
char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x;
}
bool operator<(Node a,Node b) {return a.dis > b.dis;}
void add(int u,int v,int w)
{
f[++ cnt].to = v;
f[cnt].val = w;
f[cnt].next = head[u];
head[u] = cnt;
}
void Dij()
{
memset(dis,0x3f,sizeof dis);
q.push(Node{0,1});
dis[1] = 0;
while (!q.empty())
{
int u = q.top().u;
q.pop();
if (vis[u]) continue; else vis[u] = 1;
for (int i = head[u],v; i; i = f[i].next)
if (dis[f[i].to] > dis[u] + (ll)f[i].val)
dis[f[i].to] = dis[u] + (ll)f[i].val,q.push(Node{dis[f[i].to],f[i].to});
}
}
void dfs(int u,int from)
{
dfn[u] = ++ cnt;
for (int i = head[u],v; i; i = f[i].next)
{
v = f[i].to;
if (v == from) continue;
if (!dfn[v]) opt[v][0] = u,sum[v] = sum[u] + f[i].val,dfs(v,u); else
if (dfn[v] < dfn[u])
{
totlen[++ tot] = sum[u] - sum[v] + (ll)f[i].val;
for (int j = u,t; j != f[i].to;) son[f[i].to].push_back(j),col[j] = tot,t = opt[j][0],opt[j][0] = f[i].to,j = t;
}
}
if (opt[u][0] == from) son[from].push_back(u);
}
void DFS(int u) {for (int i = 0; i < son[u].size(); i ++) dep[son[u][i]] = dep[u] + 1,DFS(son[u][i]);}
ll min(ll a,ll b) {return a < b ? a : b;}
ll calc(int u,int v)
{
if (dep[u] < dep[v]) swap(u,v);
int a = u,b = v;
for (int i = 15; i >= 0; i --) if (dep[opt[a][i]] >= dep[b]) a = opt[a][i];
if (a == b) return dis[u] - dis[v];
for (int i = 15; i >= 0; i --) if (opt[a][i] != opt[b][i]) a = opt[a][i],b = opt[b][i];
if (col[a] && col[a] == col[b]) return dis[u] + dis[v] - dis[a] - dis[b] + min(abs(sum[a] - sum[b]),totlen[col[a]] - abs(sum[a] - sum[b]));
return dis[u] + dis[v] - 2 * dis[opt[a][0]];
}
int main()
{
n = read(),m = read(),Q = read();
for (int i = 1,u,v,w; i <= m; i ++) u = read(),v = read(),w = read(),add(u,v,w),add(v,u,w);
Dij();
cnt = 0;
dfs(1,1);
dep[1] = 1;
DFS(1);
for (int i = 1; i <= 15; i ++)
for (int j = 1; j <= n; j ++) opt[j][i] = opt[opt[j][i - 1]][i - 1];
while (Q --)
{
int u = read(),v = read();
printf("%lld\n",calc(u,v));
}
return 0;
}