最小瓶颈路 和 次小生成树

简介

最小生成树是图论里面一类经典问题,可以有很多种变形,其中最小瓶颈路和次小生成树就是两种比较经典的变形。最小瓶颈路就是在两个结点之间求一条最长边最短的路径,而次小生成树则是所有生成树中权值排名第二的生成树(可以和最小生成树相等)(当然也有一个问题是求严格的次小生成树, 那么这个有不同了, 详情看这类的上一篇博客)

最小瓶颈路:

给定一个加权无向图,并给定无向图中两个结点u和v,求u到v的一条路径,使得路径上边的最大权值最小。这个问题可以稍微加强一下,即求很多对结点之间的最小瓶颈路。
无向图中,任意两个结点的最小瓶颈路肯定在最小生成树上。因此, 我们可以先求出最小生成树,然后从结点u对最小生成树进行DFS直到访问到结点v,DFS过程中就可以求出最长边。这种方法非常简单,但是效率就不够高,如果结点对很多的话,我们每次都对最小生成树进行DFS就会很慢了(一次时间O(n)) 但是这样不管是求一次还是多次询问都不是最优的。
如果是单纯求一次, 我们可以通过并查集从大到小贪心的并, 然后每次判断要连的两个点是否联通, 能联通此时的就是答案 因为我们从大可以保证尽量的大, 此时能联通就是最大的最小了. 这样也是最优最好写的方法.
但是对于多次询问的, 这样显然显得很慢, 所以那个先求出最小生成树可以提供一个思路. 求出后我们预处理这颗树, 根据数据量可以有两个算法,
一是用LCA倍增维护树上任意两点之间最大的边, 可以做到nlogn的处理, logn的回答. 对于点多边多的非常适用.
二是用dp维护, dp[i][j]代表树上点i 到 j 的最大边, O(n^2)的预处理, 可以做到O(1)的回答. 对于点的数量小于5000的还是挺适用的…. 空间消耗大….
模板一(LCA倍增):

// LCA
const int maxn = 2e5 + 5;
int up[maxn][23], maxx[maxn][23];
int deep[maxn], dis[maxn];
int cnt, head[maxn];
int n, m, q;
struct node {  //  存树
    int to, next, w;
}e[maxn<<1];
void init() {
    Fill(head,-1); Fill(dis,0);
    Fill(up,0);  Fill(deep,0);
    cnt = 0;  Fill(maxx, -1);
}
void add(int u, int v, int w) {
    e[cnt] = node{v, head[u], w};
    head[u] = cnt++;
}

void dfs(int u,int fa,int d) {
    deep[u] = d + 1;
    for(int i = 1 ; i < 20 ; i ++) {
        up[u][i] = up[up[u][i-1]][i-1];
        maxx[u][i] = max(maxx[up[u][i-1]][i-1], maxx[u][i-1]);
    }
    for(int i = head[u] ; ~i ; i = e[i].next) {
        int to = e[i].to;
        if(to == fa) continue;
        dis[to] = dis[u] + e[i].w;
        up[to][0] = u;
        maxx[to][0] = e[i].w;
        dfs(to, u, d+1);
    }
}

int LCA_BZ(int u,int v) {
    int mx = 0;
    if(deep[u] < deep[v]) swap(u,v);
    int k = deep[u] - deep[v];
    for(int i = 0 ; i < 20 ; i ++) {
        if((1<<i) & k) {
            mx = max(mx, maxx[u][i]);
            u = up[u][i];
        }
    }
    if(u != v) {
        for(int i = 19 ; i >= 0 ; i --) {
            if(up[u][i] != up[v][i]) {
                mx = max(mx, max(maxx[u][i], maxx[v][i]));
                u = up[u][i];
                v = up[v][i];
            }
        }
        mx = max(mx, max(maxx[u][0], maxx[v][0]));
        u = up[u][0];
    }
    return mx;
    //return u;
}

模板二(dp预处理):(注意空间的消耗,二维的点)

int head[maxn], cnt;
int dp[maxn][maxn], mark[maxn];
struct node {
    int to, next, w;
}e[maxn<<1];
void init() {
    Fill(head, -1); cnt = 0;
    Fill(mark, 0); Fill(dp, 0);
}
void add(int u, int v, int w) {
    e[cnt] = node{v, head[u], w};
    head[u] = cnt++;
}
void dfs(int u, int fa, int v) {
    dp[fa][u] = v; mark[u] = 1;
    for (int i = head[u] ; ~i ; i = e[i].next) {
        if (mark[e[i].to]) continue;
        dfs(e[i].to, fa, max(e[i].w, dp[fa][u]));
    }
}
void solve() {
    for (int i = 1 ; i <= n ; i ++) {
        dfs(i, i, 0); Fill(mark, 0);
    }
}

次小生成树

次小生成树有很多解法, 最显然最暴力的方法就是枚举删除最小生成树的每一条边再求最小生成树, 这样肯定是正确的, 但是复杂度为O(nm), 对于不超过500个点的图还是很好用的…. 但是最优的方法是是什么了? 我们可以发现我们在原先的最小生产树上依次加边, 那么此时树上肯定有环, 那我们应该删去什么才能使答案更优了? 很明显就是加的这条边的端点在树上的最大边是多少。那么可以联系了什么了?对那就是最小瓶颈路, 上面我们已经讲了两种预处理的算法 都可以用来做. 这样复杂度就很完美了.
算法一: O(nlogn + mlogn)
算法二: O(n^2 + m)
但是注意算法二的空间消耗.

猜你喜欢

转载自blog.csdn.net/anxdada/article/details/80420071
今日推荐