BZOJ2125 最短路

原题链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2125

最短路

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

题解

哈哈我也是做过仙人掌的人啦。

其实这是一道非常板的题,我们先将仙人掌建成一棵圆方树:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovUdRAvo-1602208085019)(https://i.loli.net/2018/07/21/5b531d50f01df.png)]

对于一个环,新建一个方点,与环上的圆点连接,边的权值为圆点与深度最浅的圆点的距离。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k8Xq6vV5-1602208085022)(https://i.loli.net/2018/07/21/5b531d50efac0.png)]

这个操作可以通过魔改的 T a r j a n \mathcal{Tarjan} Tarjan来完成,正常的树边直接连即可。

建完图以后就可以使用树上的套路了,一发 d f s \mathcal{dfs} dfs下去统计离根的距离,顺便得到倍增数组(貌似圆方树必须要倍增 L C A \mathcal{LCA} LCA,表示很气)。

现在我们就得到了一棵树,根据普通树的套路,点 x x x与点 y y y的距离便为 d i s x + d i s y − 2 × d i s l c a ( x , y ) dis_x+dis_y-2\times dis_{lca(x,y)} disx+disy2×dislca(x,y)

但这可是棵圆方树啊,怎么能跟普通的树一样呢?

上述式子只有在 l c a ( x , y ) lca(x,y) lca(x,y)为圆点时成立,当 f = l c a ( x , y ) f=lca(x,y) f=lca(x,y)为方点时,设 t x t_x tx f f f x x x走一步的点,同理 t y t_y ty f f f y y y走一步得到的点,最终的答案为 d i s x + d i s y − d i s t x − d i s t y + d i s ( t x , t y ) dis_x+dis_y-dis_{t_x}-dis_{t_y}+dis(t_x,t_y) disx+disydistxdisty+dis(tx,ty) d i s ( t x , t y ) dis(t_x,t_y) dis(tx,ty) t x , t y t_x,t_y tx,ty在仙人掌环上的距离。

代码

代码非常简短。

#include<bits/stdc++.h>
using namespace std;
const int M=2e4+5;
struct sd{int to,len;};
int n,m,q,df,tot,dfn[M],low[M],dad[M],val[M],S[M],id[M],dep[M],J[M][21],dis[M],tx,ty;
vector<sd>mmp[M],T[M];
void loop(int f,int t,int w)
{
	int pre=w,cot=0,i;
	for(i=t;i!=dad[f];i=dad[i])S[i]=pre,pre+=val[i],id[i]=cot++;
	S[++tot]=S[f];S[f]=0;
	for(i=t;i!=dad[f];i=dad[i])T[tot].push_back((sd){i,min(S[tot]-S[i],S[i])}),T[i].push_back((sd){tot,min(S[tot]-S[i],S[i])});
}
void tarjan(int v,int f)
{
	dfn[v]=low[v]=++df;dad[v]=f;int to;
	for(int i=mmp[v].size()-1;i>=0;--i)
	{
		to=mmp[v][i].to;if(to==f)continue;
		if(!dfn[to])val[to]=mmp[v][i].len,tarjan(to,v),low[v]=min(low[v],low[to]);
		else low[v]=min(low[v],dfn[to]);
		if(low[to]>dfn[v])T[v].push_back((sd){to,mmp[v][i].len});
	}
	for(int i=mmp[v].size()-1;i>=0;--i)
	{
		to=mmp[v][i].to;
		if(dad[to]!=v&&dfn[to]>dfn[v])loop(v,to,mmp[v][i].len);
	}
}
void dfs(int v,int f,int d,int l)
{
	dep[v]=d,dis[v]=l,J[v][0]=f;int to;
	for(int i=1;(1<<i)<=d;++i)J[v][i]=J[J[v][i-1]][i-1];
	for(int i=T[v].size()-1;i>=0;--i)
	{
		to=T[v][i].to;if(to==f)continue;
		dfs(to,v,d+1,l+T[v][i].len);
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);int d=dep[x]-dep[y];
	for(int i=0;(1<<i)<=d;++i)if(d&(1<<i))x=J[x][i];
	if(x==y)return x;
	for(int i=20;i>=0;--i)if((1<<i)<=dep[x]&&J[x][i]!=J[y][i])x=J[x][i],y=J[y][i];
	tx=x;ty=y;
	return J[x][0];
}
void in()
{
	int a,b,c;scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;++i)scanf("%d%d%d",&a,&b,&c),mmp[a].push_back((sd){b,c}),mmp[b].push_back((sd){a,c});
}
void ac()
{
	tot=n;tarjan(1,0);dfs(1,0,1,0);
	int x,y,f,ans;
	while(q--)
	{
		scanf("%d%d",&x,&y);f=lca(x,y);
		if(f<=n)printf("%d\n",dis[x]+dis[y]-2*dis[f]);
		else
		{
			ans=dis[x]-dis[tx]+dis[y]-dis[ty];
			if(id[tx]<id[ty])ans+=min(S[tx]+S[f]-S[ty],S[ty]-S[tx]);
			else ans+=min(S[ty]+S[f]-S[tx],S[tx]-S[ty]);
			printf("%d\n",ans);
		}
	}
}
int main(){in();ac();}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值