解题报告:LibreOJ #136 最小瓶颈路

版权声明:转载请附带原文链接,请勿随意删除原文内容,允许少量格式和/或内容修改,谢谢! https://blog.csdn.net/weixin_37661548/article/details/87688203

前言

  • 感谢巨佬LRL52的帮助与指导。

题目链接

参考代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1005,MAXM = 100005;

struct edge
{
	int to,nxt,wgt;
}info[MAXM << 1];

struct input
{
	int from,to,wgt;
	friend bool operator < (const input &opa,const input &opb)
	{
		return opa.wgt < opb.wgt;
	}
}tmp[MAXM];

int n,m,k,e,ui,vi,wi,mst;
int head[MAXN],f[MAXN],dep[MAXN],fa[MAXN][20],maxPath[MAXN][20],lg[MAXN];

inline void addedge(int from,int to,int wgt)
{
	info[++e].to = to;
	info[e].wgt = wgt;
	info[e].nxt = head[from];
	head[from] = e;
}

void dfs(int u,int f,int d)
{
	fa[u][0] = f;
	dep[u] = d;
	for (int i = 1;i <= lg[d];++i)
	{
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
		maxPath[u][i] = max(maxPath[u][i - 1],maxPath[fa[u][i - 1]][i - 1]);
	}
	for (int i = head[u];i;i = info[i].nxt)
	{
		int v = info[i].to;
		if (v == f) continue;
		maxPath[v][0] = info[i].wgt;
		dfs(v,u,d + 1);
	}
}

int LCA(int u,int v)
{
	if (dep[u] < dep[v]) swap(u,v);
	while (dep[u] != dep[v]) u = fa[u][lg[dep[u] - dep[v]]];
	if (u == v) return u;
	for (int i = lg[dep[u]];i >= 0;--i)
	{
		if (fa[u][i] != fa[v][i])
		{
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

int find(int opa)
{
	return f[opa] == opa ? opa : f[opa] = find(f[opa]);
}

inline void merge(int opa,int opb)
{
	f[find(opa)] = find(opb);
}

void kruskal()
{
	int cnt = 0;
	for (int i = 1;i <= m;++i)
	{
		int u = tmp[i].from;
		int v = tmp[i].to;
		int w = tmp[i].wgt;
		if (find(u) == find(v)) continue;
		merge(u,v);
		addedge(u,v,w);
		addedge(v,u,w);
		mst += w;
		cnt++;
	}
}

int calc(int u,int v,int lca)
{
	int res = -0x3f3f3f3f;
	while (dep[u] != dep[lca])
	{
		res = max(res,maxPath[u][lg[dep[u] - dep[lca]]]);
		u = fa[u][lg[dep[u] - dep[lca]]];
	}
	while (dep[v] != dep[lca])
	{
		res = max(res,maxPath[v][lg[dep[v] - dep[lca]]]);
		v = fa[v][lg[dep[v] - dep[lca]]];
	}
	return res;
}

void init()
{
	scanf("%d%d%d",&n,&m,&k);
	for (int i = 1;i <= m;++i)
	{
		qread(tmp[i].from);
		qread(tmp[i].to);
		qread(tmp[i].wgt);
	}
	sort(tmp + 1,tmp + m + 1);
	lg[0] = -1;
	for (int i = 1;i <= n;++i)
	{
		lg[i] = lg[i >> 1] + 1;
		f[i] = i;
	}
}

void work()
{
	kruskal();
	dfs(1,0,0);
	for (int i = 1;i <= k;++i)
	{
		qread(ui);
		qread(vi);
		if (find(ui) != find(vi))
		{
			printf("-1\n");
			continue;
		}
		int lca = LCA(ui,vi);
		int ans = calc(ui,vi,lca);
		qwrite(ans);
		putchar('\n');
	}
}

int main()
{
	init();
	work();
	return 0;
}

分析

本题LCA模板

  • 最小瓶颈路,即图上两点 u , v u,v 间的一条路径,满足这条路径上最大边权值在所有 u v u→v 的路径的最大边权值中最小。

  • 最小瓶颈路一定存在于最小生成树上。作者并不会严格的数学证明,但网上似乎没有证明(或许是太显然了 q w q qwq ),作者觉得可以用Kruskal求最小生成树来伪证一发 q w q qwq

    已知Kruskal将图上所有边排序。

    假如此时起点 s s 与终点 t t 位于两个不同的边集( s S 1 , t S 2 , S 1 S 2 = s ∈ S_1,t ∈ S_2,S_1 ∩S_2 = ∅ ),加入边 ( u , v ) , ( u S 1 , v S 2 ) (u,v),(u∈S_1,v∈S_2) ,使得起点和终点联通,由于事先将边排序,则边 ( u , v ) (u,v) 必定在起点到终点的路径上权值最大的边(之一)。此时起点到终点之间有唯一路径。

    假如存在边 ( u 2 , v 2 ) (u_2,v_2) ,连接后可以使得起点到终点之间的路径数增加,由于事先将边排序,则边 ( u 2 , v 2 ) (u_2,v_2) 权值一定大于(等于)边 ( u , v ) (u,v) 的权值,则边 ( u , v ) (u,v) 是所有 u v u→v 的路径的最大边权值中最小的(之一)。

    根据Kruskal求最小生成树的算法流程,加入的所有 n 1 n - 1 条边组成最小生成树,则边 ( u , v ) (u,v) 存在于最小生成树上,原命题得证。

  • 那么,我们先用Kruskal求出MST(最小生成树),再利用 u , v u,v 的LCA得到 u v u→v 的路径,并且在维护倍增数组时,维护节点 u u 到其 2 i 2^i 级父亲节点之间的最长边权值即可。

当然,利用上面的证明,也可以用Kruskal求解本题,代码实现就简单很多了,所以作者就不给出代码。注意这只适用于数据比较小的情况。

若有谬误,敬请斧正。

猜你喜欢

转载自blog.csdn.net/weixin_37661548/article/details/87688203
136