2125: 最短路

2125: 最短路

Time Limit: 1 Sec   Memory Limit: 259 MB
Submit: 616   Solved: 263
[ 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

HINT

对于100%的数据,N<=10000,Q<=10000

Source

[ Submit][ Status][ Discuss]



仙人掌图上的最短路

如果题给图是一棵树,用倍增很容易解决

现在是仙人掌,,不过也能用类似倍增的做法

把原图略加修改

对于每个环,将dfs时第一个遍历到的点作为这个环的根,

环上其它点到这个点连边,边权为这个点到作为根的这个点的最短路

找环的话用tarjan缩起来,,

每个点维护dis为走到1号点的距离

最后分类讨论,两个点在这棵新树上的位置关系

一般情况下,最短路为dis[x] - dis[y] - 2*dis[lca(x,y)]

如果两点的LCA为某个作为某环根的点,那么这两个点一起走的时候可能会出现在同一个环

此时设x到lca路径在该环上的点为fx,同理fy

ans = dis[x] + dis[y] - dis[fx] - dis[fy] + Dis(fx,fy)

其中Dis(i,j)为同一环上两点的最短路

这个东西,一开始找环时处理每个点到根那个点,只沿着一个方向走的距离

计算就很方便了


。。第一次写调了有点久

首先要注意,每条边只属于一个环,这东西等价于是只属于一个点双连通分量。。

所以tarjan最好不要以点为主来找环,因为一个点可以同时属于多个点双

于是,,网上参考了别人的代码,以边为主搞tarjan。。这样就好写多了

注意LCA的时候每次fx,fy都要清零,因为有xy刚好是自上而下一条链的情况--

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;

const int maxn = 2E5 + 10;

struct E{
	int x,y,w; E(){}
	E(int x,int y,int w): x(x),y(y),w(w){}
};

struct E2{
	int to,w; E2(){}
	E2(int to,int w): to(to),w(w){}
};

int n,m,q,dfs_clock,fx,fy,scc,dis[maxn],bel[maxn],dfn[maxn]
	,low[maxn],dist[maxn],tot[maxn],fa[maxn][15],L[maxn];
bool Mark[maxn];

vector <E2> v[maxn];
vector <E2> v2[maxn];
stack <E> s;
queue <int> Q;

void Build_scc(int A,int B)
{
	++scc; Mark[A] = 1;
	while (s.top().x != A && s.top().y != B) {
		E e = s.top(); s.pop();
		bel[e.x] = scc; tot[scc] += e.w;
		dist[e.x] = dist[e.y] + e.w;
		Q.push(e.x); 
	}
	E e = s.top(); s.pop(); tot[scc] += e.w;
	if (Q.empty()) v2[A].push_back(E2(B,tot[scc]));
	
	while (!Q.empty()) {
		int k = Q.front(); Q.pop();
		int w = min(dist[k],tot[scc] - dist[k]);
		v2[A].push_back(E2(k,w));
	}
}

void Dfs1(int x,int from)
{
	dfn[x] = low[x] = ++dfs_clock;
	for (int i = 0; i < v[x].size(); i++) {
		E2 e = v[x][i];
		if (!dfn[e.to]) {
			s.push(E(x,e.to,e.w)); 
			Dfs1(e.to,x);
			if (dfn[x] <= low[e.to]) Build_scc(x,e.to);
			low[x] = min(low[x],low[e.to]);
		}
		else if (e.to != from && dfn[e.to] < low[x]) low[x] = dfn[e.to],s.push(E(x,e.to,e.w)); 
	}
}

void Dfs2(int x,int from)
{
	for (int i = 1; i < 15; i++) fa[x][i] = fa[fa[x][i-1]][i-1];
	for (int i = 0; i < v2[x].size(); i++) {
		E2 e = v2[x][i];
		L[e.to] = L[x] + 1; fa[e.to][0] = x;
		dis[e.to] = dis[x] + e.w;
		Dfs2(e.to,x);
	}
}

int LCA(int p,int q)
{
	fx = fy = 0;
	if (L[p] < L[q]) swap(p,q);
	for (int j = 14; j >= 0; j--)
		if (L[p] - (1<<j) >= L[q])
			p = fa[p][j];
	if (p == q) return p;
	for (int j = 14; j >= 0; j--)
		if (fa[p][j] != fa[q][j])
			p = fa[p][j],q = fa[q][j];
	fx = p; fy = q;
	return fa[p][0];
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> n >> m >> q;
	while (m--) {
		int x,y,w; scanf("%d%d%d",&x,&y,&w);
		v[x].push_back(E2(y,w));
		v[y].push_back(E2(x,w));
	}
	Dfs1(1,0);
	L[1] = 1; Dfs2(1,0);
	while (q--) {
		int x,y; scanf("%d%d",&x,&y);
		int lca = LCA(x,y);
		if (Mark[lca] && bel[fx] == bel[fy] && bel[fx]) {
			int G = abs(dist[fx] - dist[fy]);
			G = min(G,tot[bel[fx]] - G);
			printf("%d\n",dis[x] + dis[y] - dis[fx] - dis[fy] + G);
		}
		else printf("%d\n",dis[x] + dis[y] - 2*dis[lca]);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值