例题5.21 邦德 UVa11354

1.题目描述:点击打开链接

2.解题思路:本题利用LCA+MST解决。根据题意,我们需要高效的求解指定的u,v之间的最小瓶颈路(即最大的危险系数尽量小的路径)。根据生成树的结论,最小瓶颈路一定在最小生成树上。由于需要高效的处理很多查询。因此我们需要做预处理,然后在最小生成树上求最近公共祖先。

首先把最小生成树转化为有根树,然后设fa[i]表示结点i的父亲,cost[i]表示结点i和父亲之间的边权。L[i]表示结点i的深度(根结点深度为0)。求解完毕这些数组后,接下来令anc[i][j]表示结点i的第2^j级祖先的编号(j==0时候就是fa[i],如果第2^j祖先不存在,设为-1),maxcost[i][j]表示结点i和第2^j级祖先之间路径上的最大权值。那么不难推出下面的2个关系式

(1) anc(i,j)=anc(anc(i,j-1),j-1);

(2)maxcost(i,j)=max{maxcost(i,j-1),maxcost(anc(i,j-1),j-1)};

第一个关系式表示结点i的第2^j级祖先是它的第2^(j-1)级祖先的第2^(j-1)级祖先。第二个关系式表示结点i与第2^j级祖先之间路径上的最大权值等于结点j到第2^(j-1)级祖先的最大权值,和第2^(j-1)级祖先到第2^j级祖先的最大权值之间的较大者。不难得知,这样预处理的时间复杂度是O(NlogN)级别的。

预处理结束后,查询操作就可以非常快速的实现了,如果我们希望查询u-v之间路径上的最大权值。那么在求解LCA的时候就可以动态的更新答案,具体细节详见代码。查询的时间复杂度为O(logN)。

3.代码:

#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<list>
#include<complex>
#include<functional>
using namespace std;

#define me(s) memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
#define pb push_back
typedef long long ll;
typedef pair <int,int> P;

const int N=100000+10;

const int logmaxn=20;
const int INF=1e9;

struct LCA
{
    int n;
    int fa[N];
    int cost[N];
    int L[N];
    int anc[N][logmaxn];
    int maxcost[N][logmaxn];

    void init()
    {
        for(int i=0;i<n;i++)
        {
            anc[i][0]=fa[i];
            maxcost[i][0]=cost[i];
            for(int j=1;(1<<j)<n;j++)anc[i][j]=-1;
        }
         for(int j=1;(1<<j)<n;j++)
            for(int i=0;i<n;i++)
            if(anc[i][j-1]!=-1)
        {
            int a=anc[i][j-1];
            anc[i][j]=anc[a][j-1];
            maxcost[i][j]=max(maxcost[i][j-1],maxcost[a][j-1]);
        }
    }
    int query(int p,int q)
    {
        int tmp,log,i;
        if(L[p]<L[q])swap(p,q);
        for(log=1;(1<<log)<=L[p];log++);
        log--;
        int ans=-INF;
        for(int i=log;i>=0;i--)
            if(L[p]-(1<<i)>=L[q]) //先让p和q处于同一层
        {
            ans=max(ans,maxcost[p][i]);
            p=anc[p][i];
        }
        if(p==q)return ans; //如果发现处于同一层后,恰好是同一个结点,那么LCA就是p
        for(int i=log;i>=0;i--)
            if(anc[p][i]!=-1&&anc[p][i]!=anc[q][i])//否则,让p,q同时往上爬,保证爬的时候始终处于同一层
        {
            ans=max(ans,maxcost[p][i]);p=anc[p][i];
            ans=max(ans,maxcost[q][i]);q=anc[q][i];
        }
        ans=max(ans,cost[p]); 
        ans=max(ans,cost[q]);
        return ans; //LCA为fa[p](或fa[q])
    }
};

LCA solver;

int pa[N];
int findset(int x){return pa[x]==x?x:pa[x]=findset(pa[x]);}

vector<int>g[N],C[N];

void dfs(int u,int fa,int level)//把MST转化为有根树
{
    solver.L[u]=level;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v!=fa)
        {
            solver.fa[v]=u;
            solver.cost[v]=C[u][i];
            dfs(v,u,level+1);
        }
    }
}

struct Edge
{
    int x,y,d;
    bool operator<(const Edge&rhs)const
    {
        return d<rhs.d;
    }
};

const int maxm=100000;
Edge e[maxm];

int main()
{
    int kase=0;
    int n,m,x,y,d,q;
    while(~scanf("%d%d",&n,&m)&&n)
    {
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&x,&y,&d);
            e[i]=Edge{x-1,y-1,d};
        }
        sort(e,e+m);
        for(int i=0;i<n;i++){pa[i]=i;g[i].clear();C[i].clear();}
        for(int i=0;i<m;i++)
        {
            int x=e[i].x,y=e[i].y;
            int d=e[i].d;
            int u=findset(x),v=findset(y);
            if(u!=v)
            {
                pa[u]=v;
                g[x].push_back(y);C[x].push_back(d);
                g[y].push_back(x);C[y].push_back(d);
            }
        }
        solver.n=n;
        dfs(0,-1,0);
        solver.init();
        if(kase++)puts("");
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",solver.query(x-1,y-1));
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值