【2018 BUAA ACM选拔赛 最短路】建图 | BFS分层 | 最短路 | SPFA+SLF+LLL | N

【2018 校ACM选拔赛】最短路

时间限制: 5000 ms 内存限制: 131072 kb
总通过人数: 16 总提交人数: 29

Tags:最短路问题 SPFA(+SLF+LLL) BFS分层 链式前向星

题目描述

n n n 个点的无向有根树,以 1 1 1 号点为根,走一条边需要花费相应的代价。

任意深度相差为 1 1 1 的点之间可以相互跳跃,花费代价为 p p p ,求 s s s 走到 t t t 的最小代价。


输入

T T T 组。第一行输入组数 T T T

对于每组数据,包含若干行,第一行四个整数 n , p , s , t n,p,s,t n,p,s,t

接下来 n − 1 n−1 n1 行描述这棵树,每行三个整数 u , v , w u,v,w u,v,w,表示走一条连接 u u u v v v 号点的边的代价为 w w w

其中, 1 ≤ T ≤ 20 , 1 ≤ n ≤ 1 0 5 , 1 ≤ s , t , u , v ≤ n , 0 ≤ p , w ≤ 1 0 9 1≤T≤20,1≤n≤10^5,1≤s,t,u,v≤n,0≤p,w≤10^9 1T201n105,1s,t,u,vn,0p,w109



输出

对于每组数据,输出一行,格式为 Case #number: result,其中 n u m b e r number number 表示这是第 n u m b e r number number 组数据,而 r e s u l t result result 为答案。


输入样例

2
2 1 1 2
1 2 3
7 999999999 2 7
1 2 1000000000
1 3 1000000000
2 4 999999998
3 5 1000000000
5 6 1000000000
6 7 999999998

输出样例

Case #1: 1
Case #2: 2999999995



分析

本题关键在于建图。难度不大,但是细节很容易出错。

首先规规矩矩地建树(边数是结点数-1):
  • 没什么好说的。由于是稀疏图,所以应该用链式前向星或者 s t d : : v e c t o r std::vector std::vector 实现的邻接表存图


然后考虑如何实现跳跃,从而建图
  • 为了实现跳跃,需要允许在树的层间进行中转。下面是最直接想到的方法,直接加边:

    图片1
    但是仅仅像这样,把层数差 1 1 1所有点对之间直接连边是不行的。这样添加边的数目最坏情况(比如除了根结点就两层,那每层都近似是 V / 2 V/2 V/2 个点,一共有 V 2 / 8 V^2/8 V2/8 条边需要加)将达到 V 2 V^2 V2 数量级,而这道题 V ≤ 1 0 5 V≤10^5 V105 V 2 V^2 V2 肯定会爆。


  • 所以为了实现跳跃,需要在树的每两层之间加两个额外的结点用于中转,而不是直接加边:

    图片2
    通过这种中转实现层间的全连接,最多连边不超过 4 ∗ V 4*V 4V(最多每个点连不超过四条线),在接受范围内

  • 如上图,对于原来的树上任一结点(黑色结点) u u u,如果其有父节点 f f f,那么就有中转结点 L L L(可以形象地把它画在 u u u 层和 f f f 层之间的左侧)使得存在一条路径 f → L → u f\rightarrow L\rightarrow u fLu,其长度之和为跳跃花费。
    - 比如结点 4 4 4,其父节点为 2 2 2,可利用中转结点 7 7 7,实现 2 → 7 → 4 2\rightarrow 7\rightarrow 4 274。不妨让 2 → 7 2\rightarrow 7 27 距离为 q / 2 q/2 q/2 7 → 4 7\rightarrow 4 74距离为 q − q / 2 q-q/2 qq/2,这样就实现了向下中转的目的。这样, 2 、 3 2、3 23 都可以通过 7 7 7 中转到达 4 4 4

  • 同样地,右边还有中转结点 R R R,使得存在一条路径 u → R → f u\rightarrow R\rightarrow f uRf,其长度也为跳跃花费;

  • 这样的话就能完美实现层间跳跃,并且边数也不会太大~(实际上边数仍然是 Θ ( n ) \Theta(n) Θ(n) 的)

  • 然后通过找规律(这里还需要知道原来树上每个点的层数,这个 B F S BFS BFS分层 即可)即可确定给出每个点它左上、右上、左下、右下的中转结点的编号

  • 再像上面距离那样规定 f → L f\rightarrow L fL 长度为 p / 2 p/2 p/2 L → u L\rightarrow u Lu 长度为剩余部分; f → R f\rightarrow R fR 长度为 p / 2 p/2 p/2 R → u R\rightarrow u Ru 长度为剩余部分,就可以完整地把这些有向的中转边都加进去了。


然后就可以愉快地跑SPFA了(边数是和顶点数同数量级的,SPFA更快,亲测):
  • 时间复杂度:建树 Θ ( N ) \Theta(N) Θ(N) B F S BFS BFS 分层 Θ ( N ) \Theta(N) Θ(N),加中转点建图 Θ ( N ) \Theta(N) Θ(N) S P F A SPFA SPFA O ( k N ) O(kN) O(kN),总的 O ( k N ) O(kN) O(kN)
  • 空间复杂度:多个 Θ ( N ) \Theta(N) Θ(N),还是 Θ ( N ) \Theta(N) Θ(N)


AC代码

首先是没有 SLF + LLL 的版本:

#include <stdio.h>
#include <string.h>

#define add_edge(e, u, v, d) \
  	do{ \
		edge[e].next = head[u]; \
		edge[e].dest = v; \
		edge[e].dist = d; \
		head[u] = e; \
	} while (0)

#define LL long long
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}

constexpr int MV(3e5+7);
constexpr LL INF(0x3f3f3f3f3f3f3f3fLL);

struct Edge
{
	int dest;
	int dist;
	int next;
} edge[MV * 4];
int head[MV], tot;

int depth[MV], max_depth;

LL dist_src[MV];
bool inq[MV];
int queue[MV];

void read_edge(const int V)
{
	memset(head+1, 0, sizeof(*head) * 3 * V);
	tot = 2*(V-1);
	for (int e=1; e<=tot; ++e)
	{
		int u, v, d;
		sc(u)sc(v)sc(d)

		add_edge(e, u, v, d);
		++e;
		add_edge(e, v, u, d);
	}
}

void bfs(const int V, const int root)
{
	memset(inq+1, 0, sizeof(*inq) * V);
	inq[root] = true;
	depth[root] = 1;

	int h = 0, t = 0;
	queue[t++] = root;
	while (h != t)
	{
		const int u = queue[h++];
		for (int i=head[u]; i; i=edge[i].next)
		{
			const int v = edge[i].dest;
			if (!inq[v])
			{
				inq[v] = true;
				queue[t++] = v;
				depth[v] = depth[u] + 1;
				if (max_depth < depth[v])
					max_depth = depth[v];
			}
		}
	}
}

void add_vertex(int &V, const int k)
{
	const int VV = V + 2 * (max_depth - 1);
	int k1 = k>>1;
	int k2 = k-k1;
	for (int v=1; v<=V; ++v)
	{
		const int LU = V+depth[v]-1, RU = VV-depth[v]+2,
				  LD = V+depth[v],   RD = VV-depth[v]+1;
		if (depth[v] != 1)
		{
			++tot;
			add_edge(tot, LU, v, k1);
			++tot;
			add_edge(tot, v, RU, k2);
		}
		if (depth[v] != max_depth)
		{
			++tot;
			add_edge(tot, RD, v, k1);
			++tot;
			add_edge(tot, v, LD, k2);
		}
	}
	V = VV;
}

void spfa(const int V, const int s)
{
	memset(inq+1, false, sizeof(*inq) * V);
	memset(dist_src+1, 0x3f, sizeof(*dist_src) * V);
	inq[s] = true;
	dist_src[s] = 0;

	int h = 0, t = 0;
	queue[t++] = s;
	while (h != t)
	{
		const int u = queue[h++];
		inq[u] = false;

		for (int i=head[u]; i; i=edge[i].next)
		{
			const auto v = edge[i].dest;
			const auto duv = edge[i].dist;
			if (dist_src[v] > dist_src[u] + duv)
			{
				dist_src[v] = dist_src[u] + duv;
				if (!inq[v])
				{
					inq[v] = true;
					queue[t++] = v;
				}
			}
		}
	}
}

int main()
{
	int T;
	sc(T)
	for (int _=1; _<=T; ++_)
	{
		int V, k, s, t;
		sc(V)sc(k)sc(s)sc(t)

		read_edge(V);
		bfs(V, 1);
		add_vertex(V, k);	// V changed
		spfa(V, s);

		printf("Case #%d: %lld\n", _, dist_src[t]);
	}
}



然后是加了 SLF + LLL 的版本(为了避免数组越界用了一些奇技淫巧):

#include <stdio.h>
#include <string.h>

#define add_edge(e, u, v, d) \
  	do{ \
		edge[e].next = head[u]; \
		edge[e].dest = v; \
		edge[e].dist = d; \
		head[u] = e; \
	} while (0)

#define LL long long
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}

constexpr int MV(3e5+7);
constexpr LL INF(0x3f3f3f3f3f3f3f3fLL);

struct Edge
{
	int dest;
	int dist;
	int next;
} edge[MV * 4];
int head[MV], tot;

int depth[MV], max_depth;

LL dist_src[MV];
bool inq[MV];
int _queue[MV * 10], *queue = _queue + MV * 5;

void read_edge(const int V)
{
	memset(head+1, 0, sizeof(*head) * 3 * V);
	tot = 2*(V-1);
	for (int e=1; e<=tot; ++e)
	{
		int u, v, d;
		sc(u)sc(v)sc(d)

		add_edge(e, u, v, d);
		++e;
		add_edge(e, v, u, d);
	}
}

void bfs(const int V, const int root)
{
	memset(inq+1, 0, sizeof(*inq) * V);
	inq[root] = true;
	depth[root] = 1;

	int h = 0, t = 0;
	queue[t++] = root;
	while (h != t)
	{
		const int u = queue[h++];
		for (int i=head[u]; i; i=edge[i].next)
		{
			const int v = edge[i].dest;
			if (!inq[v])
			{
				inq[v] = true;
				queue[t++] = v;
				depth[v] = depth[u] + 1;
				if (max_depth < depth[v])
					max_depth = depth[v];
			}
		}
	}
}

void add_vertex(int &V, const int k)
{
	const int VV = V + 2 * (max_depth - 1);
	int k1 = k>>1;
	int k2 = k-k1;
	for (int v=1; v<=V; ++v)
	{
		const int LU = V+depth[v]-1, RU = VV-depth[v]+2,
				  LD = V+depth[v],   RD = VV-depth[v]+1;
		if (depth[v] != 1)
		{
			++tot;
			add_edge(tot, LU, v, k1);
			++tot;
			add_edge(tot, v, RU, k2);
		}
		if (depth[v] != max_depth)
		{
			++tot;
			add_edge(tot, RD, v, k1);
			++tot;
			add_edge(tot, v, LD, k2);
		}
	}
	V = VV;
}

void spfa(const int V, const int s)
{
	memset(inq+1, false, sizeof(*inq) * V);
	memset(dist_src+1, 0x3f, sizeof(*dist_src) * V);
	inq[s] = true;
	dist_src[s] = 0;

	int h = 0, t = 0, q_size = 0;
	queue[t++] = s;
	LL sum;
	while (h != t)
	{
		while (dist_src[queue[h]] * q_size > sum)	// LLL
			queue[t++] = queue[h], ++h;

		const int u = queue[h++];
		inq[u] = false;
		sum -= dist_src[u], --q_size;

		for (int i=head[u]; i; i=edge[i].next)
		{
			const auto v = edge[i].dest;
			const auto duv = edge[i].dist;
			if (dist_src[v] > dist_src[u] + duv)
			{
				dist_src[v] = dist_src[u] + duv;
				if (!inq[v])
				{
					inq[v] = true;
					if (h < t && dist_src[queue[h]] > dist_src[v])	// SLF
						queue[--h] = v;
					else
						queue[t++] = v;
					sum += dist_src[v], ++q_size;
				}
			}
		}
	}
}

int main()
{
	int T;
	sc(T)
	for (int _=1; _<=T; ++_)
	{
		int V, k, s, t;
		sc(V)sc(k)sc(s)sc(t)

		read_edge(V);
		bfs(V, 1);
		add_vertex(V, k);	// V changed
		spfa(V, s);

		printf("Case #%d: %lld\n", _, dist_src[t]);
	}
}

div2 只有十分钟啦,踩点写完博客,马上开打~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值