圆方树(bzoj 2125: 最短路)

版权声明:本文为博主原创文章,你们可以随便转载 https://blog.csdn.net/Jaihk662/article/details/83751470

 

2125: 最短路

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

什么是圆方树:

对于一棵仙人掌

  • 仙人掌的所有点 → 圆方树中的所有圆点
  • 仙人掌的所有不在环中的边 → 连接圆方树中两个圆点的边
  • 仙人掌中的其中一个环 →  圆方树中的其中一个方点, 这个方点向当前环中的所有圆点连边,边的长度 = 这个点到这个环中最接近根的那个点的距离

一张非常形象的图,来自于一个课件

其实看了这个图应该就什么都懂了,一些性质也很容易被发现

仙人掌两点的最短路:

先建立出圆方树,关于如何建立圆方树:双联通分量

之后将问题转化成圆方树的LCA,LCA可以用树链剖分解决

考虑求LCA的两种情况:

  1. 如果两点的LCA是圆点:可以证明仙人掌树上的最短路 = 圆方树上的最短路
  2. 如果两点的LCA是方点:说明这两点会在环上相遇,先求出这两点到环的距离,然后在环上分类讨论下就可以了

搞定

剩下的代码中有注释

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
typedef struct Road
{
	int v;
	int len;
}Road;
Road now;
vector<Road> G[20005], T[20005];
int n, t, cnt, Time[20005], low[20005], tp[20005], dep[20005], fa[20005], a[20005], dis[20005];
struct RST
{
	int fa[20005], size[20005], hson[20005], top[20005], dep[20005], dis[20005];
	int t, rak[20005], id[20005], cir[20005], zn[20005];
	void Sech1(int u, int p)		//对树先来一套基本操作,顺便求出每个节点的重儿子
	{
		int v, i;
		fa[u] = p, dep[u] = dep[p]+1;
		size[u] = 1;
		for(i=0;i<T[u].size();i++)
		{
			v = T[u][i].v;
			if(v==p)
				continue;
			dis[v] = dis[u]+T[u][i].len;
			Sech1(v, u);
			size[u] += size[v];
			if(size[v]>size[hson[u]])
				hson[u] = v;
		}
	}
	void Sech2(int u, int p)			//重链剖分
	{
		int i, v;
		top[u] = p;
		rak[u] = ++t, id[t] = u;
		if(hson[u])
			Sech2(hson[u], p);
		for(i=0;i<T[u].size();i++)
		{
			v = T[u][i].v;
			if(v==fa[u] || v==hson[u])
				continue;
			Sech2(v, v);
		}
	}
	int LCA(int u, int v)
	{
		while(top[u]^top[v])
		{
			if(dep[top[u]]<dep[top[v]])
				v = fa[top[v]];
			else
				u = fa[top[u]];
		}
		if(dep[u]<dep[v])
			return u;
		return v;
	}
	int Jump(int u, int lca)		//跳到lca的那个环上
	{
		int ret;
		while(top[u]!=top[lca])
			ret = top[u], u = fa[top[u]];
		if(u==lca)			//如果lca正好是一条链的尾端,那么上一个链的链头肯定就是当前要求的点
			return ret;
		return id[rak[lca]+1];		//如果lca不在链的尾端,那么当前要求的点一定是lca的重儿子!要不怎么会在一条链上
	}
	int Query(int u, int v)
	{
		int lca, A, B, d1, d2;
		lca = LCA(u, v);
		if(lca<=n)		//如果LCA是圆点,说明他们不在环上相遇,直接返回它们与lca的距离即可
			return dis[u]+dis[v]-2*dis[lca];
		else		//在环上相遇
		{
			A = Jump(u, lca), B = Jump(v, lca);
			d1 = dis[A]-dis[lca], d2 = dis[B]-dis[lca];
			if(zn[A]==0)
				d1 = cir[lca]-d1;
			if(zn[B]==0)
				d2 = cir[lca]-d2;		//d1和d2分别表示A和B离当前环中最接近根的那个点的距离
			return dis[u]-dis[A]+dis[v]-dis[B]+min(abs(d1-d2),cir[lca]-abs(d1-d2));
		}
	}
}RST;
void CreateS(int u, int v, int len)
{
	int R, sum, i, D;
	D = R = 0, sum = len, cnt++;		//cnt为当前新建的方点
	for(i=v;1;i=fa[i])
	{
		a[++R] = i;
		if(i==u)
			break;
		sum += dis[i]-dis[fa[i]];
	}
	for(i=1;i<=R/2;i++)
		swap(a[i], a[R-i+1]);
	RST.cir[cnt] = sum;
	for(i=1;i<=R;i++)
	{
		now.len = min(D, sum-D);
		now.v = a[i], T[cnt].push_back(now);
		now.v = cnt, T[a[i]].push_back(now);
		RST.zn[a[i]] = (now.len==D);
		D += dis[a[i+1]]-dis[a[i]];
	}
}
void Tarjan(int u, int p)
{
	int i, v;
	Time[u] = low[u] = ++t;
	fa[u] = p, dep[u] = dep[p]+1;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i].v;
		if(v==p)
			continue;
		if(Time[v]==0)
		{
			dis[v] = dis[u]+G[u][i].len;
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
		}
		else
			low[u] = min(low[u], Time[v]);
		if(low[v]>Time[u])
		{
			T[u].push_back(G[u][i]);
			now.v = u, now.len = G[u][i].len;
			T[v].push_back(now);
		}
	}
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i].v;
		if(fa[v]!=u && Time[v]>Time[u])
			CreateS(u, v, G[u][i].len);
	}
}
int main(void)
{
	int i, m, T, x, y;
	scanf("%d%d%d", &n, &m, &T), cnt = n;
	for(i=1;i<=m;++i)
	{
		scanf("%d%d%d", &x, &y, &now.len);
		now.v = y, G[x].push_back(now);
		now.v = x, G[y].push_back(now);
	}
	Tarjan(1, 0);
	RST.Sech1(1, 0);
	RST.Sech2(1, 1);
	while(T--)
	{
		scanf("%d%d", &x, &y);
		printf("%d\n", RST.Query(x, y));
	}
	return 0;
}
/*
15 18 0
10 15 15 9 9 14 14 2
10 13 13 12 12 11 11 10 10 9 9 2
3 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1
*/

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/83751470