图论 - 生成树 - 最小瓶颈路

定义

最小瓶颈路:两个结点之间的最长边最短的一条路径
最小瓶颈路一定在最小生成树上

学习资料

  1. 最小瓶颈路 By 青石巷
  2. 【洛谷2245】 星际导航 (最小瓶颈路) By ZAGER

分类及方法

在上面那篇blog中,分为了单次查询与多次查询

单次查询:可以直接求出最小生成树,在求的过程中加一个判断来记录s,t间的最长边就好(一般用Kruskal);

多次查询:求出最小生成树,再用LCA求出两点间的最长边(一般用倍增),还有的人用kruskal重构数做,还有直接用树链剖分(线段树维护)做的

例题

单次查询:luogu1396 营救

多次查询:luogu2245星际导航 / luogu1967货车运输 / bzoj 3732network / loj 136 最小瓶颈路 (在我心中这几道题是一样的

加强版:loj137 最小瓶颈路(加强版)

单次查询

luogu1396营救

题目传送门:luogu1396

题目描述
“咚咚咚……”“查水表!”原来是查水表来了,现在哪里找这么热心上门的查表员啊!小明感动的热泪盈眶,开起了门……

妈妈下班回家,街坊邻居说小明被一群陌生人强行押上了警车!妈妈丰富的经验告诉她小明被带到了t区,而自己在s区。

该市有m条大道连接n个区,一条大道将两个区相连接,每个大道有一个拥挤度。小明的妈妈虽然很着急,但是不愿意拥挤的人潮冲乱了她优雅的步伐。所以请你帮她规划一条从s至t的路线,使得经过道路的拥挤度最大值最小。

输入格式
第一行四个数字n,m,s,t。

接下来m行,每行三个数字,分别表示两个区和拥挤度。

(有可能两个区之间有多条大道相连。)

输出格式
输出题目要求的拥挤度。

输入输出样例
输入 #1 复制
3 3 1 3
1 2 2
2 3 1
1 3 3
输出 #1 复制
2

说明/提示
数据范围
30% n<=10
60% n<=100
100% n<=10000,m<=2n,拥挤度<=10000

题目保证1<=s,t<=n且s<>t,保证可以从s区出发到t区。

样例解释:

小明的妈妈要从1号点去3号点,最优路线为1->2->3。

代码:

/****************************
User:Mandy.H.Y
Language:c++
Problem:1396
Algorithm:Kruskal
****************************/

#include<bits/stdc++.h>

using namespace std;

const int maxn = 1e4 + 5;
const int maxm = 2e4 + 5;

int n,m,size,cnt,s,t;
int father[maxn];

struct Edge{
	int u,v,w;
}edge[maxm];

template<class T>inline void read(T &x){
	x = 0;bool flag = 0;char ch = getchar();
	while( ! isdigit(ch)) flag |= ch == '-',ch = getchar();
	while(isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48),ch = getchar();
	if(flag) x = -x;
}

template<class T>void putch(const T x){
	if(x > 9) putch(x / 10);
	putchar(x % 10 | 48);
}

template<class T>void put(const T x){
	if(x < 0) putchar('-'),putch(-x);
	else putch(x);
}

void file(){
	freopen("monkey.in","r",stdin);
}

void eadd(int u,int v,int w){
	edge[ ++ size].v = v;
	edge[size].u = u;
	edge[size].w = w;
}

void readdata(){
	read(n);read(m);read(s);read(t);
	for(int i = 1;i <= m; ++ i){
		int u,v,w;
		read(u);read(v);read(w);
		eadd(u,v,w);
	}
}

bool cmp(const Edge &a,const Edge &b){
	return a.w < b.w;
}

int find(int x){
	return father[x] == x ? x :father[x] = find(father[x]);
}

void merge(int x,int y){
	father[find(x)] = find(y);
}

void work(){
	for(int i = 1;i <= n; ++ i) father[i] = i;
	sort(edge + 1,edge + size + 1,cmp);
	int ans = 0;
	for(int i = 1;i <= size; ++ i){
		int w = edge[i].w,u = edge[i].u,v = edge[i].v;
		if(find(u) != find(v)){
			merge(u,v);
			ans = w;
		}
		if(find(s) == find(t)) {
			put(w);return;
		}
	}
}

int main(){
//	file();
	readdata();
	work();
	return 0;
}

多次查询

luogu2245 星际导航

题目传送门:luogu2245
题目描述
sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好。为了方便起见,我们可以认为宇宙是一张有N 个顶点和M 条边的带权无向图,顶点表示各个星系,两个星系之间有边就表示两个星系之间可以直航,而边权则是航行的危险程度。

sideman 现在想把危险程度降到最小,具体地来说,就是对于若干个询问(A, B),sideman 想知道从顶点A 航行到顶点B 所经过的最危险的边的危险程度值最小可能是多少。作为sideman 的同学,你们要帮助sideman 返回家园,兼享受安全美妙的宇宙航行。所以这个任务就交给你了。

输入输出格式
输入格式:
第一行包含两个正整数N 和M,表示点数和边数。

之后 M 行,每行三个整数A,B 和L,表示顶点A 和B 之间有一条边长为L 的边。顶点从1 开始标号。

下面一行包含一个正整数 Q,表示询问的数目。

之后 Q 行,每行两个整数A 和B,表示询问A 和B 之间最危险的边危险程度的可能最小值。

输出格式:
对于每个询问, 在单独的一行内输出结果。如果两个顶点之间不可达, 输出impossible。

输入输出样例
输入样例#1:
4 5
1 2 5
1 3 2
2 3 11
2 4 6
3 4 4
3
2 3
1 4
1 2
输出样例#1:
5
4
5
说明
对于40% 的数据,满足N≤1000,M≤3000,Q≤1000。

对于 80% 的数据,满足N≤10000,M≤105,Q≤1000。

对于 100% 的数据,满足N≤105,M≤3×105,Q≤105,L≤109。数据不保证没有重边和自环。

分析

三种方法:

  1. 最小生成树 + LCA查询(一般倍增)
  2. Kruskal重构树
  3. 树链剖分(线段树维护最值)

注意事项:

  1. 图不一定连通
  2. 最小瓶颈路模板题
  3. 注意赋初值
  4. 先求再变 。

代码

/**************************
User:Mandy.H.Y
Language:c++
Problem:luogu2245
Algorithm:kruskal + lca(doubling)
**************************/

//图不一定连通……
//最小瓶颈路模板题 
 
#include<bits/stdc++.h>

using namespace std;

const int maxn = 1e5 + 5;
const int maxm = 3e5 + 5;

int n,m,size,size1,q;
int first[maxn],father[maxn],dep[maxn];
int fa[maxn][20],dis[maxn][20];
bool vis[maxn];

struct Edge{
	int v,w,u;
}edge[maxm];

struct Edge2{
	int v,nt,w;
}edge1[maxn << 1];

template<class T>inline void read(T &x){
	x = 0;bool flag = 0;char ch = getchar();
	while(!isdigit(ch)) flag |= ch == '-',ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
	if(flag) x=-x;
}

template<class T>void putch(const T x){
	if(x > 9) putch(x / 10);
	putchar(x % 10 | 48);
}

template<class T>void put(const T x){
	if(x < 0) putchar('-'),putch(-x);
	else putch(x);
}

void file(){
	freopen("2245.in","r",stdin);
}

void eadd(int u,int v,int w){
	edge[ ++ size].v = v;
	edge[size].w = w;
	edge[size].u = u;
}

void eadd1(int u,int v,int w){
	edge1[ ++ size1].v = v;
	edge1[size1].w = w;
	edge1[size1].nt = first[u];
	first[u] = size1;
}

bool cmp(const Edge &a,const Edge &b){
	return a.w < b.w;//记得return 
}

void readdata(){
	read(n);read(m);
	for(int i = 1;i <= m; ++ i){
		int u,v,w;
		read(u);read(v);read(w);
		eadd(u,v,w);
	}
}

int find(int x){
	return father[x] == x ? x : father[x] = find(father[x]);
}

void merge(int x,int y){
	father[find(x)] = find(y);
}

void kruskal(){
	int cnt = 0;
	for(int i = 1;i <= size; ++i){
		int u = edge[i].u,v = edge[i].v,w = edge[i].w;
		if(find(u) != find(v)){
			merge(u,v);
			++cnt;
			eadd1(u,v,w);
			eadd1(v,u,w);
		}
		if(cnt == n - 1) break; 
	}
}

void dfs(int u,int f,int d){
	
	fa[u][0] = f;vis[u] = 1;dep[u] = d;//注意赋初值 
	
	for(int i = 1;i < 18; ++ i) //这里不是1~n; 
		fa[u][i] = fa[fa[u][i-1]][i-1],
		dis[u][i] = max(dis[u][i-1],dis[fa[u][i-1]][i-1]);
	
	for(int i = first[u];i;i = edge1[i].nt){
		int v = edge1[i].v;
		int w = edge1[i].w;
		if(v == f) continue;
		dis[v][0] = w;
		dfs(v,u,d + 1);
	}
}

int lca(int u,int v){
	if(dep[u] < dep[v]) swap(u,v);
	int ans = -2e9; 
	for(int i = 18;i >= 0; -- i)//数组下标最大为18 
		if(fa[u][i] && dep[fa[u][i]] >= dep[v]) 
			ans = max(ans,dis[u][i]),
			u = fa[u][i];//先求再变!!! 
	if(u == v) return ans == -2e9 ? 0 : ans;//return ans 
	//防止u与v一开始就相等 
	for(int i = 18;i >= 0; -- i){
		if(fa[u][i] != fa[v][i]){
			ans = max(ans,dis[u][i]);
			ans = max(ans,dis[v][i]);
			u = fa[u][i],v = fa[v][i];
		}
	}
	ans = max(ans,max(dis[u][0],dis[v][0]));
	return ans;
}

void work(){
	sort(edge + 1,edge + 1 + size,cmp);
	for(int i = 1;i <= n; ++ i) father[i] = i;
	kruskal();
	for(int i = 1;i <= n; ++ i){
		if(!vis[i]) dfs(i,0,1);
	}//深度从1开始,防止0混入 
	
	read(q);
	for(int i = 1;i <= q; ++ i){
		int a,b;
		read(a);read(b);
		if(find(a) != find(b)) puts("impossible");
		else {
			int ans = lca(a,b);
			put(ans);puts("");
		}
	}
}

int main(){
//	file();
	readdata();
	work();
	return 0;
}
luogu1967货车运输

题目描述
A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入描述
第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行 3 个整数 x、y、z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意:x 不等于 y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意:x 不等于 y。

输出描述
输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。

样例输入
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3

样例输出
3
-1
3

数据范围及提示
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q < 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q < 1,000;
对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q < 30,000,0 ≤ z ≤ 100,000。

/*************************
User:Mandy.H.Y
Language:c++
Problem:luogu1967 track
Algorithm:
Date:2019.8.12
*************************/

//最大瓶颈路…… 
//套路都是一样的 

#include<bits/stdc++.h>
#define Max(x,y) ((x) > (y) ? (x) : (y))
#define Min(x,y) ((x) < (y) ? (x) : (y))

using namespace std;

const int maxn = 1e4 + 5;
const int maxm = 5e4 + 5;
const int inf = 2e9;

int n,m,size,cnt;
int top[maxn],father[maxn][16],dep[maxn];
int dis[maxn][16];
int first[maxn];
bool vis[maxn];

struct Val{
	int u,v,w;
}val[maxm];

struct Edge{
	int v,w,nt;
}edge[maxn << 1];

template<class T>inline void read(T &x){
	x = 0;bool flag = 0;char ch = getchar();
	while(!isdigit(ch)) flag |= ch == '-',ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
	if(flag) x = -x;
}

template<class T>void putch(const T x){
	if(x > 9) putch(x / 10);
	putchar(x % 10 | 48);
}

template<class T>void put(const T x){
	if(x < 0) putchar('-'),putch(-x);
	else putch(x);
}

void file(){
	freopen("track.in","r",stdin);
	freopen("track.out","w",stdout);
}

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

void readdata(){
	read(n);read(m);
	for(int i = 1;i <= m; ++ i){
		int u,v,w;
		read(u);read(v);read(w);
		add(u,v,w);
	}
}

bool cmp(const Val &a,const Val &b){
	return a.w > b.w;//记得return 
}

int find(int x){
	return top[x] == x ? x : top[x] = find(top[x]);
}

void merge(int x,int y){
	top[find(x)] = find(y); 
}

void eadd(int u,int v,int w){
	edge[++ size].v = v;
	edge[size].w = w;
	edge[size].nt = first[u];
	first[u] = size;
}



void kruskal(){
	sort(val + 1,val + 1 + m,cmp);
	for(int i = 1;i <= n; ++ i)  top[i] = i;//初始化 
	int tot = 0;
	for(int i = 1;i <= cnt; ++ i){
		int v = val[i].v,u = val[i].u,w = val[i].w;
		if(find(u) != find(v)){
			merge(u,v);
			++ tot;
			eadd(u,v,w);eadd(v,u,w);
		}
		if(tot == n - 1) break;
	}
}

void dfs(int u,int f,int d){
	vis[u] = 1; dep[u] = d;
	father[u][0] = f;

	for(int i = 1;i <= 14; ++ i) {
		father[u][i] = father[father[u][i-1]][i-1];
		dis[u][i] = Min(dis[u][i - 1],dis[father[u][i - 1]][i - 1]); 
	}
	
	for(int i = first[u];i;i = edge[i].nt){
		int v = edge[i].v,w = edge[i].w;
		if(v == father[u][0]) continue;
		dis[v][0] = w;
		dfs(v,u,d + 1);
	}
}

int lca(int u,int v){
	if(dep[u] < dep[v]) swap(u,v);
	
	int ans = inf;
	
	for(int i = 14; i >= 0; -- i)
		if(father[u][i] && dep[father[u][i]] >= dep[v]){
			ans = Min(ans,dis[u][i]);
			u = father[u][i];
		}
	
	if(u == v) return ans;
	
	for(int i = 14;i >= 0; -- i){
		if(father[u][i] != father[v][i]){
			ans = Min(ans,Min(dis[u][i],dis[v][i]));
			u = father[u][i];
			v = father[v][i];
		}
	}
	
	return Min(ans,Min(dis[u][0],dis[v][0]));
		
}

void work(){
	kruskal();
	memset(dis,0x3f3f3f3f,sizeof(dis));//记得初始化 
	for(int i = 1;i <= n; ++ i)
		if(!vis[i]) dis[i][0] = 0,dfs(i,0,1);
	int q;
	read(q);
	
	for(int i = 1;i <= q; ++ i){
		int u,v;
		read(u);read(v);
		if(find(u) != find(v)) {puts("-1"); continue;}
		int ans = lca(u,v);
		put(ans);puts("");
	}
}

int main(){
//	file();
	readdata();
	work();
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值