UVA 11354 BOND(并查集)

题目链接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))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值