定义
最小瓶颈路:两个结点之间的最长边最短的一条路径
最小瓶颈路一定在最小生成树上
学习资料
- 最小瓶颈路 By 青石巷
- 【洛谷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。数据不保证没有重边和自环。
分析:
三种方法:
- 最小生成树 + LCA查询(一般倍增)
- Kruskal重构树
- 树链剖分(线段树维护最值)
注意事项:
- 图不一定连通
- 最小瓶颈路模板题
- 注意赋初值
- 先求再变 。
代码:
/**************************
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;
}