BZOJ 2125: 最短路

题目链接

题解

考虑转化成树上问题,那么显然需要构建圆方树。原点和方点的距离如何定义呢?考虑在仙人掌上两个点找最短距离的过程,不考虑返祖边的话就是一直跳到lca,返祖变的作用就是在那个环上的时候有机会更快跳到环顶。于是对于每条返祖边对应的环,环上所有点到方点的距离先定位它到环顶端的最短距离。这样,如果两个点的lca是原点,那么直接计算;否则说明它们的lca在一个环上,多记录一下每个点在环上的编号即可。
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10,M=17;
int n,m,q,c,t=1;
int h[N],to[N*2],nx[N*2],w[N*2];
void add(int u,int v,int x){
	t++; to[t]=v; nx[t]=h[u]; h[u]=t; w[t]=x;
}
int dn[N],ds[N],l[N],fa[N],fl[N];
bool vi[N*2],is[N*2];
void tar(int u){
	dn[u]=++c;
	for(int e=h[u],v;e;e=nx[e]){
		v=to[e]; if(vi[e]) continue; vi[e]=vi[e^1]=1;
		if(!dn[v]) ds[v]=ds[u]+w[e],fa[v]=u,fl[v]=e,tar(v);
		else if(dn[v]<dn[u]){
			n++; l[n]=ds[u]-ds[v]+w[e];
			add(n,v,0),add(v,n,0); is[e]=is[e^1]=1;
			for(int i=u;i!=v;i=fa[i]){
				int x=min(ds[i]-ds[v],l[n]-(ds[i]-ds[v]));
				add(i,n,x); add(n,i,x); 
				is[fl[i]]=is[fl[i]^1]=1;
			}
		}
	}
}
int dp[N],dis[N],f[N][M];
void dfs(int u){
	for(int i=1;i<M;i++) f[u][i]=f[f[u][i-1]][i-1];
	for(int v,e=h[u];e;e=nx[e]) if(!is[e] && to[e]!=f[u][0]) 
		v=to[e],dp[v]=dp[u]+1,dis[v]=dis[u]+w[e],f[v][0]=u,dfs(v);
}
int find(int u,int v){ //cout<<u<<" "<<v<<endl;
	if(dp[u]<dp[v]) swap(u,v);
	for(int i=M-1;~i;i--) if(dp[f[u][i]]>=dp[v]) u=f[u][i]; //cout<<u<<" "<<v<<endl;
	for(int i=M-1;~i;i--) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];//cout<<i<<endl;
	return (u!=v)?f[u][0]:u;
}
int get(int u,int v){
	for(int i=M-1;~i;i--) if(dp[f[u][i]]>dp[v]) u=f[u][i];
	return u;
}
int main()
{
    //freopen("G.in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m>>q; int u,v,x;
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&u,&v,&x); add(u,v,x),add(v,u,x);
	}
	m=n; tar(1); dp[1]=1; dfs(1);
	/*for(int i=1;i<=n;i++){
		cout<<i<<" dis="<<dis[i]<<" dp="<<dp[i]<<" f0="<<f[i][0]<<" f1="<<f[i][1]<<endl;
	}*/
	while(q--){
		scanf("%d %d",&u,&v);
		int lca=find(u,v); //cout<<lca<<endl;
		if(lca<=m) printf("%d\n",dis[u]+dis[v]-2*dis[lca]);
		else{ //cout<<1<<endl;
			int fu=get(u,lca),fv=get(v,lca);
			int ans=dis[u]-dis[fu]+dis[v]-dis[fv];
			int len=abs(ds[fu]-ds[fv]);
			printf("%d\n",ans+min(len,l[lca]-len));
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值