JZOJ1914. 【2011集训队出题】最短路

题目大意

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

分析

这道图的题目可以思考如何用树上的做法来解决这道题。在简洁的题目里得出了一个极为重要的信息每个点只会出现在一个环中。首先当然是先跑一遍Dij,处理出从编号为1的点到各个点的距离,然后我们不妨先处理出每个环,并标上号,并处理出环上的边权和(有一些细节需谨慎处理),然后思考对于每组询问,分为如下三种情况处理即可。

  • a a a b b b在一条链上则答案显然为 a b s ( d i s [ a ] − d i s [ b ] ) abs(dis[a] - dis[b]) abs(dis[a]dis[b])
  • a a a b b b L C A LCA LCA相对的那两个儿子节点在同一个环中那么答案为
    d i s [ u ] + d i s [ v ] − d i s [ a ] − d i s [ b ] + m i n ( a b s ( s u m [ a ] − s u m [ b ] ) , t o t l e n [ c o l [ a ] ] − a b s ( s u m [ a ] − s u m [ b ] ) ) dis[u] + dis[v] - dis[a] - dis[b] + min(abs(sum[a] - sum[b]),totlen[col[a]] - abs(sum[a] - sum[b])) dis[u]+dis[v]dis[a]dis[b]+min(abs(sum[a]sum[b]),totlen[col[a]]abs(sum[a]sum[b]))
    其中 u u u, v v v分别为 a a a b b b L C A LCA LCA的相对的那个儿子节点, s u m [ a ] sum[a] sum[a]为从环上钦定的节点到 a a a的距离,所以 s u m [ a ] − s u m [ b ] sum[a] - sum[b] sum[a]sum[b]即为环上两点间的距离, t o t l e n [ a ] totlen[a] totlen[a]表示编号为 a a a的环的边权和, c o l [ a ] col[a] col[a]表示 a a a号点所在的环的编号。那么这个答案还是比较显然的 m i n ( a b s ( s u m [ a ] − s u m [ b ] ) , t o t l e n [ c o l [ a ] ] − a b s ( s u m [ a ] − s u m [ b ] ) ) min(abs(sum[a] - sum[b]),totlen[col[a]] - abs(sum[a] - sum[b])) min(abs(sum[a]sum[b]),totlen[col[a]]abs(sum[a]sum[b]))就是比较在环上从 a a a走到 b b b是按照原顺序比较近还是按照另一个方向走过去比较近。
  • a a a b b b L C A LCA LCA相对的那两个儿子节点不在同一个环中那么答案为
    d i s [ u ] + d i s [ v ] − 2 ∗ d i s [ o p t [ a ] [ 0 ] ] dis[u] + dis[v] - 2 * dis[opt[a][0]] dis[u]+dis[v]2dis[opt[a][0]]
    其中 o p t [ a ] [ 0 ] opt[a][0] opt[a][0] a a a往上跳20步所到的点,在此处即为 u u u v v v点的 L C A LCA LCA
  • 如果口糊得不太清楚可以自己画一下图挺好理解的。

Code

#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;

const int N = 1e4 + 10;
const int M = 6e5 + 10;
struct Edge{
	int to,next,val;
} f[M << 1];
struct Node{
	int dis,u;
};
int Q,n,m,cnt,head[N],dep[N],opt[N][16],dfn[N],totlen[N],tot,col[N];
ll dis[N],sum[N];
bool vis[N];
vector<int> son[N];
priority_queue<Node> q;

int read()
{
	int x = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x;
}

bool operator<(Node a,Node b) {return a.dis > b.dis;}

void add(int u,int v,int w)
{
	f[++ cnt].to = v;
	f[cnt].val = w;
	f[cnt].next = head[u];
	head[u] = cnt;
}

void Dij()
{
	memset(dis,0x3f,sizeof dis);
	q.push(Node{0,1});
	dis[1] = 0;
	while (!q.empty())
	{
		int u = q.top().u;
		q.pop();
		if (vis[u]) continue; else vis[u] = 1; 
		for (int i = head[u],v; i; i = f[i].next)
			if (dis[f[i].to] > dis[u] + (ll)f[i].val) 
				dis[f[i].to] = dis[u] + (ll)f[i].val,q.push(Node{dis[f[i].to],f[i].to});
	}
}

void dfs(int u,int from)
{
	dfn[u] = ++ cnt;
	for (int i = head[u],v; i; i = f[i].next)
	{
		v = f[i].to;
		if (v == from) continue;
		if (!dfn[v]) opt[v][0] = u,sum[v] = sum[u] + f[i].val,dfs(v,u); else 
		if (dfn[v] < dfn[u])
		{
			totlen[++ tot] = sum[u] - sum[v] + (ll)f[i].val;
			for (int j = u,t; j != f[i].to;) son[f[i].to].push_back(j),col[j] = tot,t = opt[j][0],opt[j][0] = f[i].to,j = t;
		}
 	}
 	if (opt[u][0] == from) son[from].push_back(u);
}

void DFS(int u) {for (int i = 0; i < son[u].size(); i ++) dep[son[u][i]] = dep[u] + 1,DFS(son[u][i]);}

ll min(ll a,ll b) {return a < b ? a : b;}

ll calc(int u,int v)
{
	if (dep[u] < dep[v]) swap(u,v);
	int a = u,b = v;
	for (int i = 15; i >= 0; i --) if (dep[opt[a][i]] >= dep[b]) a = opt[a][i];
	if (a == b) return dis[u] - dis[v];
	for (int i = 15; i >= 0; i --) if (opt[a][i] != opt[b][i]) a = opt[a][i],b = opt[b][i];
	if (col[a] && col[a] == col[b]) return dis[u] + dis[v] - dis[a] - dis[b] + min(abs(sum[a] - sum[b]),totlen[col[a]] - abs(sum[a] - sum[b]));
	return dis[u] + dis[v] - 2 * dis[opt[a][0]]; 
}

int main()
{
	n = read(),m = read(),Q = read();
	for (int i = 1,u,v,w; i <= m; i ++) u = read(),v = read(),w = read(),add(u,v,w),add(v,u,w);
	Dij();
	cnt = 0;
	dfs(1,1);
	dep[1] = 1;
	DFS(1);
	for (int i = 1; i <= 15; i ++)
		for (int j = 1; j <= n; j ++) opt[j][i] = opt[opt[j][i - 1]][i - 1];
	while (Q --)
	{
		int u = read(),v = read();
		printf("%lld\n",calc(u,v));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值