题目链接:https://vjudge.net/problem/SCU-3098(UVA好像挂了,所以其实是SCU上的,这就是vjudge香的地方了)
题目大意:
n个城市,通过m条道路连接,每条道路都有一个危险值,假设从城市s到城市t的一条通路为:道路a→道路b→道路c 则,这条通路的危险值被定义为道路a、b、c中危险值的最大值,现在将进行Q次询问:城市s到城市t的最小危险值为多少?
分析:
①最小生成树包含了任意两点间权值最小的通路,所以城市s到城市t的最小危险值可以由最小生成树求得。因为要进行多次询问,所以想到用一个数据结构来储存信息,也就是并查集,并查集其实会改变树的结构,但是这道题两点间的危险值是由通路上最大的危险值决定的,所以可以用,具体解释如下:
(这一部分略显无聊,且我解释的也不是很清楚,因此可以跳过)
以题目给的样例为例:
4 5 1 2 10 1 3 20 1 4 100 2 4 30 3 4 10
第一步 连接点1和 点2 (以1作为2的子节点)
第二步 连接点3和 点4 (以3作为4的子节点)
第三步 连接点1和 点3 (因为1的根节点是2,而3的根节点是4,所以在并查集中是让2作为 4的子节点)
本来应该连接1-3这条边的,但是其实并查集连接的是2-4这条边。
所以并查集其实会改变最小生成树的结构
但是这道题仍然可以使用并查集:
我们用一个数组danger来记录每个点到其父节点的危险值,则1-3间的danger值被赋到了2-4上
将最小生成树的点集V任意分出两部分V1和V2,则从集合V1和集合V2之间有且只有一条边可以连通
(去掉最小生成树中的任意一条边,连通分量都将增加)
那现在假设并查集要合并集合V1和集合V2了,以上图为例:
集合V1={1,2},集合V2={3,4}
则我们把要连接的1、3之间的边的权值赋给2、4也是无所谓的,因为集合V1和集合V2内部的点互相到达的答案没有改变,而从V1中的某一点到达V2中的某一点终归是要经过这条边的,所以在计算通路上的最大危险值时仍然考虑到了这条边
②求得最小生成树之后如何寻找两个结点通路上的危险最小值?
LCA:lowest common ancestor
最小生成树中任意两点s,t之间有且仅有一条通路,且这条通路必然是:点s→点s和点t的公共祖先→点t
朴素的求lca的做法:O(N)复杂度
从点s向上遍历到根节点t,用vis数组记录被访问过的点,然后从点t向上遍历,如果发现某点vis值为0,则该节点即二者的最小公共祖先
AC代码:
#include <iostream>
#include <cstdio>
#include <string.h>
#include <string>
#include <algorithm>
using namespace std;
typedef struct{
int a,b,d;
}road;
road r[100005];
int pre[50005];
int rk[50005];
int danger[50005]; //记录结点i和其父节点间的危险值
int vis[50005];
void init(){
for(int i=1;i<=50000;i++)
pre[i]=i,rk[i]=0;
}
int root_find(int x){
return pre[x]==x?x:root_find(pre[x]);
}
bool join(int x,int y,int road_danger){
int rx=root_find(x);
int ry=root_find(y);
if(rx!=ry){
if(rk[rx]<rk[ry]) pre[rx]=ry,danger[rx]=road_danger;
else if(rk[ry]<rk[rx]) pre[ry]=rx,danger[ry]=road_danger;
else pre[rx]=ry,rk[ry]++,danger[rx]=road_danger;
}
else return 0;
return 1;
}
bool cmp(road x,road y){
return x.d<y.d;
}
int main()
{
int N,M;
while(~scanf("%d%d",&N,&M)){
init();
for(int i=0;i<M;i++)
scanf("%d%d%d",&r[i].a,&r[i].b,&r[i].d);
sort(r,r+M,cmp);
int num=0;
for(int i=0;i<M;i++){
if(join(r[i].a,r[i].b,r[i].d)) num++;
if(num==N-1) break;
}
int Q;
scanf("%d",&Q);
for(int i=1;i<=N;i++) vis[i]=-1;
while(Q--){
int s,t;
scanf("%d%d",&s,&t);
int cost_s_to_root=0;
vis[s]=0;
int temp=s;
while(pre[temp]!=temp){
cost_s_to_root=max(cost_s_to_root,danger[temp]);
temp=pre[temp];
vis[temp]=cost_s_to_root;
}
temp=t;
int ans=0;
while(vis[temp]==-1){
ans=max(ans,danger[temp]);
temp=pre[temp];
if(vis[temp]!=-1) {
ans=max(ans,vis[temp]);
break;
}
}
ans=max(ans,vis[temp]);
printf("%d\n",ans);
temp=s;
while(pre[temp]!=temp)
vis[temp]=-1,temp=pre[temp];
vis[temp]=-1;
}
printf("\n");
}
return 0;
}