最小瓶颈路与次小生成树

简介:

最小生成树是图论里面一类经典问题,可以有很多种变形,其中最小瓶颈路和次小生成树就是两种比较经典的变形。最小瓶颈路就是在两个结点之间求一条最长边最短的路径,而次小生成树则是所有生成树中权值排名第二的生成树(可以和最小生成树相等)。下面我们分别来看看这两个问题。

最小瓶颈路:

给定一个加权无向图,并给定无向图中两个结点u和v,求u到v的一条路径,使得路径上边的最大权值最小。这个问题可以稍微加强一下,即求很多对结点之间的最小瓶颈路。

无向图中,任意两个结点的最小瓶颈路肯定在最小生成树上。因此,对于第一个问题,我们可以先求出最小生成树,然后从结点u对最小生成树进行DFS直到访问到结点v,DFS过程中就可以求出最长边。这种方法非常简单,但是效率就不够高,如果结点对很多的话,我们每次都对最小生成树进行DFS就会很慢了(一次时间O(n))。

我们可以在查询前进行预处理,将所有结点对的最长边保存在一个maxcost数组中,之后每次查询直接访问数组即可,只需要O(1)时间。具体的方法是将无根的最小生成树转成有根树,转换过程中同时计算maxcost[u][v]。当访问一个新结点u时,考虑所有已经访问过的结点j,对maxcost[j][u]进行更新,公式如下:

maxcost[j][u]=max(maxcost[j][fa[u]], maxcost[j][u])

其中,fa数组中保存结点在有根树中的父节点。

代码实现:

代码实现有两种具体方式,一种基于prim算法,一种则为kruskal算法。

Prim算法:

如果使用prim算法,那么我们可以在求解最小生成树的过程中将有根树建立起来,并且同时求所有结点对maxcost。

int prim(int s)
{
    int res=0;
    memset(maxcost,0,sizeof(maxcost));
    for(int i=1;i<=n;i++)
        vis[i] = 0, d[i] = INF, pre[i]=i;

    d[s]=0;
    for(int i=0;i<n;i++)
    {
        int maxx=INF, index=-1;
        for(int j=1;j<=n;j++)
        {
            if(!vis[j]&&d[j]<maxx)
            {
                maxx=d[index=j];
            }
        }
        if(index==-1)
            break;

        for(int j=1;j<=n;j++)
            if(vis[j])
                maxcost[index][j] = maxcost[j][index] =
                    max(maxcost[pre[index]][j], maxx);

        res+=maxx;
        vis[index]=1;

        for(int j=1;j<=n;j++)
        {
            if(!vis[j]&&g[index][j]<d[j])
            {
                d[j] = g[index][j];
                pre[j] = index;
            }
        }
    }
    return res;
}

Kruskal算法:

如果使用的是kruskal算法,其实在算法进行时我们也可以通过并查集将最小瓶颈路求解出来,但是这种方法要求并查集不能使用路径压缩优化,所以我个人更喜欢另一种做法,即先求解出最小生成树,之后将无根树转成有根树,在转换的过程中求解maxcost。详见代码:

struct Edge{
    int u,v,w;
    Edge(int u=0,int v=0,int dist=0):u(u),v(v),w(dist){}
}e[maxm];
vector<Edge> vec[maxn];

bool cmp(const Edge& a,const Edge& b){
    return a.w<b.w;
}

int find(int x){
    return p[x]==x?x:p[x]=find(p[x]);
}

void kruskal()
{
    sort(e,e+m,cmp);
    for(int i=1;i<=n;i++)
        p[i]=i;
    int cnt=0;
    for(int i=0;i<m;i++){
        int x=find(e[i].u),y=find(e[i].v);
        if(x!=y){
            p[y]=x;
            vec[e[i].u].push_back(Edge(e[i].u, e[i].v, e[i].w));
            vec[e[i].v].push_back(Edge(e[i].v, e[i].u, e[i].w));
            if(++cnt==n-1)
                break;
        }
    }
}

void dfs(int index)
{
    vis[index]=1;
    for(int i=0;i<vec[index].size();i++)
    {
        int tmp=vec[index][i].v;
        if(!vis[tmp])
        {
            for(int j=1;j<=n;j++)
                if(vis[j])
                {
                    maxcost[j][tmp] = maxcost[tmp][j] =
                        max(maxcost[j][index], vec[index][i].w);
                }
            pre[tmp]=index;
            dfs(tmp);
        }
    }
}

这两种方法的使用情景不同,prim算法一般用于稠密图,而kruskal算一般用于稀疏图。还有一种基于LCA的算法,在UVA 11534会用到,这里就不讲了。

次小生成树:

次小生成树问题,有很多种解法,一种很容易想到的是枚举最小生成树中不在次小生成树中的一条边(因为次小生成树一定是最小生成树替换一条边构成的),然后去掉这条边之后求解最小生成树,这种方法的时间复杂度大概是O(n*m)。另一种比较高效的做法是基于回路性质的,还是枚举要加入哪条新边(u, v),加入新边之后必然形成环,我们要做的就是在u到v的路径山删掉一条边,使权值尽量小。很显然,要删除的边就是最长边,所以我们可以用最小瓶颈路的方法求出maxcost数组,之后枚举所有的新边并求解生成树权值,这些生成树中权值最小的就是次小生成树的权值,时间复杂度为O(n^2)。

这里代码就略去了,因为跟最小瓶颈路其实是一个问题,多了一个枚举每条边的过程而已。

总结:

这看上去完全不相关的两个问题,其实我们可以通过预处理出每对结点的最小瓶颈路来求解次小生成树,这种思想是值得我们借鉴的。

时间: 2024-08-04 01:06:32

最小瓶颈路与次小生成树的相关文章

最小瓶颈路 Uva 534 Frogger

说明:关于Uva的题目,可以在vjudge上做的,不用到Uva(那个极其慢的)网站去做. 最小瓶颈路:找u到v的一条路径满足最大边权值尽量小 先求最小生成树,然后u到v的路径在树上是唯一的,答案就是这条路径. Uva 534 Frogger Time Limit: 3000MS 64bit IO Format: %lld & %llu Description Freddy Frog is sitting on a stone in the middle of a lake. Suddenly h

HDU 4081Qin Shi Huang&amp;#39;s National Road System(最小生成树+最小瓶颈路)

 题意:秦始皇要修路.把N个城市用N-1条边连通.且他希望花费最小,可是这时候有一个多管闲事的道士出来说他有魔法能够帮助秦始皇变成一条路.可是仅仅能变出一条. 可是.两个人对修路的法案存在歧义,道士希望修路能够给很多其它的百姓带来福利.而秦始皇希望修路要尽量使花费小.最后,秦始皇拿出了一个公式A/B.A表示两个城市的人数,B表示出了用魔法变出来的路外.最短的总距离. 如今要你求出A/B的最大值. 思路:枚举连接哪两个城市.由于这条边是免费的.我们要求算上这条边的最小生成树.假设每次都求一次最

UVALive 5713 Qin Shi Huang&#39;s National Road System秦始皇修路(MST,最小瓶颈路)

题意: 秦始皇要在n个城市之间修路,而徐福声可以用法术位秦始皇免费修1条路,每个城市还有人口数,现要求徐福声所修之路的两城市的人口数之和A尽量大,而使n个城市互通需要修的路长B尽量短,从而使得A/B最大.问A/B最大是多少?(1000个城市) 思路: 老徐可免费修得1条路,那么剩下最多也只需要修n-2条路了,这n-2条路要尽量挑短的,而老徐的那条无所谓长短,只要两城人口尽量多即可.这是没有什么贪心策略的,因为老徐所修之路会影响MST的权值之和的大小.穷举所有城市对要O(n*n),再求次MST需要

UVA 10457 - Magic Car(最小瓶颈路)

UVA 10457 - Magic Car 题目链接 题意:m条路,每条路上必须维持速度v,现在有一辆车,启动能量和结束能量为a, b,途中消耗能量为经过路径最大速度减去最小速度,现在每次循环给定起点终点,问最小能量花费 思路:最小瓶颈路,利用kruskal去搞 代码: #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 205; con

BZOJ 3732 Network 最小瓶颈路

题目大意:给出一个无向边,很多询问,问x,y两地之间的最长路最短是多少. 思路:乍一看好像是二分啊.的确这个题二分可以做,但是时间会慢很多,有的题直接就T掉(NOIP2013货车运输).其实这个题的模型就是最小瓶颈路模型.解法就是把无向图变成一个最小生成树,然后两点之间的最长路就是满足题意的答案. CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm&g

【UVA534】Frogger 最小瓶颈路

题目大意:给定一张 N 个点的完全图,求 1,2 号节点之间的一条最小瓶颈路. 题解:可知,最小瓶颈路一定存在于最小生成树(最小瓶颈树)中.因此,直接跑克鲁斯卡尔算法,当 1,2 号节点在同一个联通块时,即可停止算法,并输出答案即可. 代码如下 #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using n

洛谷 P1967 货车运输 Label: 倍增LCA &amp;&amp; 最小瓶颈路

题目描述 A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物. 输入输出格式 输入格式: 输入文件名为 truck.in. 输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道 路. 接下来 m 行每行 3 个整数 x. y. z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z

最小瓶颈路

题目描述 给定一个包含 n 个节点和 m 条边的图,每条边有一个权值. 你的任务是回答 k 个询问,每个询问包含两个正整数 s 和 t 表示起点和终点,要求寻找从 s 到 t 的一条路径,使得路径上权值最大的一条边权值最小. 输入格式 第一行包含三个整数 n.m.k,分别表示 n 个节点, m 条路径, k 个询问. 接下来 m 行,每行三个整数 u, v, w, 表示一个由 u 到 v 的长度为 w 的双向边. 再接下来 k 行,每行两个整数 s, t,表示询问从 s 连接到 t 的所有路径中

uva 10457(最小瓶颈路)

比赛的时候读错题了,赛后非要建最小生成树然后dfs暴搜,有人告诉我不行,我还非要改一遍,改了一年,想明白了,不能保证下限,比如2,3,5能使两个点连同,4,5也能的话,就不对了,想想我也是个铁头娃 #include <iostream> #include <cstring> #include <algorithm> #include <cstdio> #include <queue> using namespace std; const int