题目:
题意:给定q个查询,每个查询包括u,求u到v的路上所有走过的边中的最大值最小,也就是最小瓶颈路
题意分析:因为是个m条边的图,并且最小瓶颈路包括在最小生成树中(因为最小生成树上任意两个点能互相到,并且最短的边都在上面)有两种情况:
1.数据比较小的时候,我们可以先建立一棵最小生成树,然后在树上dfs
2.优化:建立完最小生成树(注意不要用路径压缩,因为会破坏树的结构,导致建图出现问题)后,对于u,v点求LCA,maxcost[i][j]表示从节点 i 到 i的2^j 的父节点中所经历的最大路径,就是LCA模板上加一个更新maxcost,别的都一摸一样,其实还感觉很难,为了这个题目折腾一个上午不敢自己动手,最后看代码发现只要在LCA的基础上加个东西就好了,所以说以后还是不能怂啊,要多写代码,多练本事~
maxcost[i][j]=max(maxcost[i][j-1],maxcost[fa[i][j-1]][j-1])
然后每次查询求一遍LCA,再返回maxcost就好了,具体看代码
代码:
/*其实Kruskal生成最小生成树后,后面都是LCA的模板,只是在更新父节点的时候加上一个更新
maxcost[i][j]=max(maxcost[i][j-1],maxcost[fa[i][j-1]][j-1]),别的都没区别*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<cmath>
#include<vector>
#include<set>
#include<stack>
#define N 50005
#define M 100005
using namespace std;
int n,m,c,q;
int head[N],fa[N][23],deg[N],maxcost[N][23],f[N];
struct ljh
{
int x,y,z;
}a[M];
struct xqy
{
int next,to,w;
}e[M];
bool cmp(ljh u,ljh v)
{
return u.z<v.z;
}
int find(int x)
{
if(x!=f[x])return f[x]=find(f[x]);
return x;
}
inline void add(int x,int y,int z)
{
e[c].next=head[x];
e[c].w=z;
e[c].to=y;
head[x]=c++;
}
void kruskal()
{
c=0;
int tot=0;
for(int i=1;i<=n;i++)f[i]=i;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
int u=find(a[i].x);
int v=find(a[i].y);
if(u!=v)
{
f[u]=v;
tot++;
add(a[i].x,a[i].y,a[i].z);
add(a[i].y,a[i].x,a[i].z);
}
if(tot==n-1)break;
}
return;
}
void dfs(int u,int pre)
{
deg[u]=deg[pre]+1;
for(int i=head[u];i!=-1;i=e[i].next)
{
if(!deg[e[i].to])
{
fa[e[i].to][0]=u;
maxcost[e[i].to][0]=e[i].w;
dfs(e[i].to,u);
}
}
return;
}
int LCA(int x,int y)
{
int ans=-1;
if(deg[x]<deg[y])swap(x,y);
for(int i=0;i<22;i++)
if((deg[x]-deg[y])&(1<<i))
{
ans=max(ans,maxcost[x][i]);
x=fa[x][i];
}
if(x==y)return ans;
for(int i=22;i>=0;i--)
{
if(fa[x][i]!=fa[y][i])
{
ans=max(ans,maxcost[x][i]);
ans=max(ans,maxcost[y][i]);
x=fa[x][i];
y=fa[y][i];
}
}
ans=max(ans,maxcost[x][0]);
ans=max(ans,maxcost[y][0]);
return ans;
}
int main()
{
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int T=0;
while(~scanf("%d%d",&n,&m))
{
if(T!=0)printf("\n");
T++;
for(int i=1;i<=m;i++)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
sort(a+1,a+m+1,cmp);
kruskal();
memset(deg,0,sizeof(deg));
memset(fa,-1,sizeof(fa));
memset(maxcost,0,sizeof(maxcost));
dfs(1,1);
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(fa[i][j-1]!=-1)
{
fa[i][j]=fa[fa[i][j-1]][j-1];
maxcost[i][j]=max(maxcost[i][j-1],maxcost[fa[i][j-1]][j-1]);
}
scanf("%d",&q);
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
}
}
还有一种方法就是按秩合并,感觉代码复杂度较高,因为他是在不进行路径压缩的基础上,一个节点一个节点的向上访问的,也是类似LCA的方法,关于按秩合并与路径压缩都是并查集的优化,具体可看传送门
代码:
#include <bits/stdc++.h>
using namespace std;
#define INF 1000000009
#define N 50010
int n,m,q,x,y;
int fa[N],Rank[N],edge[N],c[N];
struct node
{
int x,y,z;
}a[100010];
int cmp(node a, node b)
{
return a.z < b.z;
}
int Find(int x)
{
return fa[x] == x ? x:Find(fa[x]);//不进行路径压缩
}
void Kruscal()
{
int s = 0,f1,f2;
for (int i=1;i<=n;i++) fa[i] = i, Rank[i] = 1;
memset(edge, 0, sizeof(edge));
for (int i=1;i<=m;i++)
{
f1 = Find(a[i].x);
f2 = Find(a[i].y);
if (f1 != f2)
{
if (Rank[f1] < Rank[f2])
{
fa[f1] = f2;
/*当两个秩不相等时,我们使具有高秩的根成为具有较低秩的根的父结点,
但秩本身保持不变。
当两个秩相同时,任选一个根作为父结点,并增加其秩的值路径压缩。*/
edge[f1] = a[i].z;
Rank[f2] = max(Rank[f2], Rank[f1]+1);
}
else
{
fa[f2] = f1;
edge[f2] = a[i].z;
Rank[f1] = max(Rank[f1], Rank[f2]+1);
}
s++;
}
if (s == n-1) break;
}
}
int query(int x, int y)
{
for (int i=1;i<=n;i++) c[i] = -1;//表示从x往上走走到i的时候路径的最大值
int tmp = 0, ans = 0;
while (1)
{
c[x] = tmp;
if (fa[x] == x) break;
tmp = max(tmp, edge[x]);
x = fa[x];
}
while (1)
{
if (c[y] >= 0){ans = max(ans,c[y]);break;}//走到了x和y的LCA的位置了
if (fa[y] == y) break;
ans = max(ans, edge[y]);
y = fa[y];
}
return ans;
}
int main()
{
bool flag = 0;
while (~scanf("%d%d", &n, &m))
{
if (flag) printf("\n");
for (int i=1;i<=m;i++)
scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);
sort(a+1,a+m+1,cmp);
Kruscal();
scanf("%d", &q);
for (int i=1;i<=q;i++)
{
scanf("%d%d", &x, &y);
printf("%d\n", query(x,y));
}
flag = 1;
}
}