题目链接UVA 11354
题目描述
给出一张n个点m条边的无向图,每条边有一个权值,有Q个询问,每个询问给出两个点s、t,找一条路,使的经过的路径上边最大权值最小。
思路
本题有多种做法,不过大多都跟并查集有关。
I 倍增
我们发现可以建一棵最小生成树,可以证明,当有一条边可以替换掉建成树的其他边时,它的权值一定比它要替换的边权值大,再用倍增处理路径,每次倍增向上走求最大值。
代码就不贴了。
复杂度O(qlog(n))
II 询问共同二分
如果只有一个询问,那么我们显然可以用二分做这道题,对每条边边权排序,二分最小答案,只使用小于等于mid的边,用并查集合并使用的边,最后判断询问的两个点是否在相同的集合里。
但问题不会这么简单,这里有多个询问,但其实也是可以二分的,我们可以尝试消维,我们将询问根据mid排序(这里的询问是一个结构体,将记录用于二分的l,r,mid(可以省略)),每次二分时依次枚举询问,同时枚举边权,由于询问单调,边权也单调,所以只要在枚举询问时更新边权的下标即可。
就像这样
FOR(i,1,q) {
if(Q[i].l>Q[i].r)continue;
Query e=Q[i];
while(edge[pos].v<=Q[i].mid&&pos<=m) {
Union(edge[pos].a,edge[pos].b);
pos++;
}
if(Getfa(e.a)==Getfa(e.b)) {
Q[i].r=Q[i].mid-1;
Ans[Q[i].id]=Q[i].mid;
Q[i].mid=(Q[i].l+Q[i].r)>>1;
} else {
Q[i].l=Q[i].mid+1;
Q[i].mid=(Q[i].l+Q[i].r)>>1;
}
}
最后问题就简单了,二分次数不会超过log(1e5)
代码
#include<cstdio>
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
#include<map>
#include<algorithm>
#include<cmath>
#include<set>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 50005
typedef long long ll;
int n,m,q;
struct node {
int a,b;
int v;
bool operator<(const node &P)const {
return v<P.v;
}
} edge[M];
struct Query {
int a,b,id;
int l,r;
int mid;
bool operator<(const Query &P)const {
return mid<P.mid;
}
} Q[M];
int Fa[M],Ans[M];
void Init() {
FOR(i,1,n)Fa[i]=i;
}
int Getfa(int v) {
return Fa[v]==v?v:Fa[v]=Getfa(Fa[v]);
}
void Union(int a,int b) {
a=Getfa(a);
b=Getfa(b);
Fa[a]=b;
}
int main() {
int cas=0;
while(scanf("%d%d",&n,&m)==2) {
if(cas)puts("");
cas=1;
FOR(i,1,m)scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].v);
sort(edge+1,edge+m+1);
scanf("%d",&q);
FOR(i,1,q)
Q[i].id=i,
Q[i].l=0,Q[i].r=1e5,Q[i].mid=1e5/2,
scanf("%d%d",&Q[i].a,&Q[i].b);
int cnt=17;
while(cnt--) {
Init();
sort(Q+1,Q+q+1);
int pos=1;
FOR(i,1,q) {
if(Q[i].l>Q[i].r)continue;
Query e=Q[i];
while(edge[pos].v<=Q[i].mid&&pos<=m) {
Union(edge[pos].a,edge[pos].b);
pos++;
}
if(Getfa(e.a)==Getfa(e.b)) {
Q[i].r=Q[i].mid-1;
Ans[Q[i].id]=Q[i].mid;
Q[i].mid=(Q[i].l+Q[i].r)>>1;
} else {
Q[i].l=Q[i].mid+1;
Q[i].mid=(Q[i].l+Q[i].r)>>1;
}
}
}
FOR(i,1,q)printf("%d\n",Ans[i]);
}
return 0;
}
边权最大值视为与n同维
则总复杂度为(n*((log(n))^2))
III 按秩合并
并查集经典的应用,可以运用于图论最大值最小值的题目,通过按秩,建立一棵高度为log(n)的树。
这道题明显可以用按秩合并,在建完树后,就可以在线处理询问,处理方式就是暴力往上跳,反正深度log(n)
这是按秩合并的代码
void Union(int a,int b,int c){
int fa=Getfa(a),fb=Getfa(b);
if(fa==fb)return;
if(Rank[fa]<Rank[fb]){
Fa[fa]=fb;
V[fa]=c;
}
else {
Fa[fb]=fa;
V[fb]=c;
if(Rank[fa]==Rank[fb])Rank[fa]++;
}
}
这是这道题的代码
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i_##END_=(b);i<=i_##END_;++i)
#define REP(i,a,b) for(int i=(a),i_##BEGIN_=(b);i>=i_##BEGIN_;--i)
#define M 50005
typedef long long ll;
int n,m,q;
struct node{
int a,b,v;
bool operator<(const node &P)const {return v<P.v;}
}edge[M];
int Rank[M],Fa[M],V[M],Vis[M],Ans[M];
void Init(){FOR(i,1,n)Fa[i]=i,Rank[i]=0,Vis[i]=-1;}
int Getfa(int v){return Fa[v]==v?v:Getfa(Fa[v]);}
void Union(int a,int b,int c){
int fa=Getfa(a),fb=Getfa(b);if(fa==fb)return;
if(Rank[fa]<Rank[fb]){Fa[fa]=fb;V[fa]=c;}
else {Fa[fb]=fa;V[fb]=c;if(Rank[fa]==Rank[fb])Rank[fa]++;}
}
int Calc(int a,int b,int id){
int ans=0;
while(1){
Ans[a]=ans;Vis[a]=id;
if(V[a]>ans)ans=V[a];
if(a==Fa[a])break;a=Fa[a];
}
ans=0;
while(1){
if(Vis[b]==id){if(Ans[b]>ans)ans=Ans[b];break;}
if(V[b]>ans)ans=V[b];b=Fa[b];
}
return ans;
}
int main(){
int cas=0;
while(scanf("%d%d",&n,&m)==2){
if(cas)puts("");cas=1;
Init();
FOR(i,1,m)scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].v);
sort(edge+1,edge+m+1);FOR(i,1,m)Union(edge[i].a,edge[i].b,edge[i].v);
scanf("%d",&q);
int a,b;
while(q--){
scanf("%d%d",&a,&b);
printf("%d\n",Calc(a,b,q));
}
}
return 0;
}
复杂度O(nlog(n))