BZOJ2125: 最短路(仙人掌两点最短路径)【圆方树】

题目描述:

给一个N个点M条边的连通无向图,满足每条边最多属于一个环,有Q组询问,每次询问两点之间的最短路径。
N , Q ≤ 10000 N,Q\le10000 N,Q10000

题目分析:

建出仙人掌的圆方树,每个点到方点的距离为它到这个点双中在圆方树上深度最小的点的最短距离(可以通过原树上到根的距离差和环长得出)。

求两点最短路径时先求LCA,如果为圆点就直接用dis[u]+dis[v]-2*dis[lca],如果为方点需要额外计算经过lca所在点双的最短路径(同样可以通过lca的两个儿子在原树上到根的距离差和环长得出)。

Code:

#include<bits/stdc++.h>
#define maxn 20005
#define maxm 40005
using namespace std;
int n,m,Q,od[maxn],cir[maxn],sz;
namespace RST{
	const int Log = 14;
	int dis[maxn],dep[maxn],f[maxn][Log+1];
	int fir[maxn],nxt[maxm],to[maxm],w[maxm],tot;
	inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
	void dfs(int u,int ff){
		dep[u]=dep[f[u][0]=ff]+1;
		for(int i=1;i<=Log;i++) f[u][i]=f[f[u][i-1]][i-1];
		for(int i=fir[u];i;i=nxt[i]) dis[to[i]]=dis[u]+w[i],dfs(to[i],u);
	}
	int query(int x,int y){
		if(dep[x]<dep[y]) swap(x,y);
		int u=x,v=y,lca,d;
		for(int i=0,d=dep[u]-dep[v];d;i++) if(d>>i&1) u=f[u][i],d^=1<<i;
		if(u==v) return dis[x]-dis[y];
		for(int i=Log;i>=0;i--) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
		if((lca=f[u][0])<=n) return dis[x]+dis[y]-2*dis[lca];
		return d=abs(od[u]-od[v]),dis[x]-dis[u]+dis[y]-dis[v]+min(d,cir[lca]-d);
	}
}
int dfn[maxn],low[maxn],tim,stk[maxn],top,cl[maxn];
int fir[maxn],nxt[maxm],to[maxm],w[maxm],tot=1;
inline void bline(int x,int y,int z){
	nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;
	nxt[++tot]=fir[y],fir[y]=tot,to[tot]=x,w[tot]=z;
}
void tarjan(int u,int ff){
	dfn[u]=low[u]=++tim,stk[++top]=u;
	for(int i=fir[u],v;i;i=nxt[i]) if(i^1^ff){
		if(!dfn[v=to[i]]){
			od[v]=od[u]+w[i],tarjan(v,i),low[u]=min(low[u],low[v]);
			if(dfn[u]==low[v]){
				cir[++sz]=cl[u],RST::line(u,sz,0);
				for(int t=-1,j=top;t!=v;) t=stk[j--],cir[sz]+=od[t]-od[stk[j]];
				do {int d=od[stk[top]]-od[u];RST::line(sz,stk[top],min(d,cir[sz]-d));} while(stk[top--]!=v);
			}
			else if(dfn[u]<low[v]) RST::line(u,v,w[i]),top--;
		}
		else cl[v]=w[i],low[u]=min(low[u],dfn[v]);
	}
}
int main()
{
	int x,y,z;
	scanf("%d%d%d",&n,&m,&Q),sz=n;
	for(int i=1;i<=m;i++) scanf("%d%d%d",&x,&y,&z),bline(x,y,z);
	tarjan(1,0);
	RST::dfs(1,0);
	while(Q--){
		scanf("%d%d",&x,&y);
		printf("%d\n",RST::query(x,y));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值