BZOJ 1977 严格次小生成树——浅析最近公共祖先(LCA)

目录

 

一.最近公共祖先(LCA)

1.定义:最近公共祖先一定是在树上的两个点最近的且公共的祖先

2.举例说明:

3.暴力爬山

4.倍增

5.Tarjan

二.例题:严格次小生成树

1.题目

1.题目描述

2.输入

3.输出

4.样例输入

5.样例输出

6.数据范围

2.题解

3.贴代码

谢谢!


一.最近公共祖先(LCA)

1.定义:最近公共祖先一定是在上的两个点最近的且公共的祖先

2.举例说明:

如图:

4和6的公共祖先是1;

3和6的公共祖先是3;

8和9的公共祖先是6。

3.暴力爬山

这种算法最慢,时间复杂度:O(n)

4.倍增

倍增是一种很好的求LCA的方法,时间复杂度:O(log_{n})

倍增爬山就是说这次爬山爬的高度是上次的两倍

我们先预处理一个数组f[i][j],表示第i个点向上爬2^{j}步所到达的点。(j最大取19就够了)

void prepare (){//MAXN=19
    f[1][0] = 0;
    for (int j = 1; j <= MAXN; j ++){
        for (int i = 1; i <= n; i ++){
            f[i][j] = f[f[i][j - 1]][j - 1];
        }
    }
}

然后进入倍增主函数。

我们首先将两个点向上爬到同一深度,然后两个点同时往上爬,直到爬到了相同的点之前为止。爬山要运用f[i][j]数组,从大到小枚举j。

为什么爬到相同的点就停了呢?因为看上面举例的图就知道,两个点最近公共祖先之后的祖先都是同样的祖先,如果爬到了相同的点就证明已经爬到了最近公共祖先及以上,那么这一步就暂时不爬,最后两个的父亲节点就是LCA。

给一个模板:

void add (int &u, int maxdep){
    for (int i = MAXN; i >= 0; i --){
        if (dep[f[u][i]] >= maxdep)
            u = f[u][i];
    }
}
int Doubly (int x, int y){
    if (dep[x] > dep[y])//两个点爬到同一深度
        add (x, dep[y]);
    if (dep[x] < dep[y])
        add (y, dep[x]);
    if (x == y)
        return x;
    for (int i = MAXN; i >= 0; i --)//两个点同时往上爬
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}

5.Tarjan

我不太擅长这种离线算法,所以点击打开链接

二.例题:严格次小生成树

1.题目

1.题目描述

小 C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 e的权值)  这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

2.输入

第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。

3.输出

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

4.样例输入

5 6

1 2 1

1 3 2

2 4 3

3 5 4

3 4 3

4 5 6

5.样例输出

11

Hint

6.数据范围

数据中无向图无自环;

50% 的数据N≤2 000 M≤3 000;

80% 的数据N≤50 000 M≤100 000;

100% 的数据N≤100 000 M≤300 000 ,边权值非负且不超过 10^9 。

2.题解

相信最小生成树大家都打过吧,但是我们却要求一棵次小生成树。

首先要把最小生成树打出来,然后我们会发现,我们任意加一条边进去会形成一个环,我们只要删掉其中的一条边就又会形成一棵树。删去的边固然是除了加进去的那一条边的最长边,那么就会形成一颗理想的次小生成树,再把所有的剩下的边都依次加进去试,就能找到严格次小生成树。

再来重点讲一下这个核心操作:删去环中比加进去的边小的最长边

首先最小生成树上的所有边一定小于或等于剩下的边。

然后我们存储最小生成树上每个点向上走2^{j}所遇到的最大边和次大边。然后在删边的时候,求出加入的边的两端点在最小生成树上的LCA,再找两端点到LCA的次大边和最大边。如果最大边的权值与加入的边相同就用次大边,否则就用最大边。

预处理最小生成树上每个点向上走2^{j}所遇到的最大边和次大边就是在倍增的预处理函数基础上再加一些东西:一个点向上走2^{j}遇到的最大与次大边在这个点向上走2^{j-1}遇到的最大和次大边,以及这个点向上走2^{j-1}到的点再向上走2^{j-1}遇到的最大和次大边。

预处理:

void prepare (){//0是最大,1是次大
    f[1][0] = 0;
    for (int j = 1; j <= MAXN; j ++)
        for (int i = 1; i <= n; i ++){
            f[i][j] = f[f[i][j - 1]][j - 1];
            Max[i][j][0] = max (Max[i][j - 1][0], Max[f[i][j - 1]][j - 1][0]);
            if (Max[i][j - 1][0] == Max[f[i][j - 1]][j - 1][0])
                Max[i][j][1] = max (Max[i][j - 1][1], Max[f[i][j - 1]][j - 1][1]);
            else if (Max[i][j - 1][0] < Max[f[i][j - 1]][j - 1][0])
                Max[i][j][1] = max (Max[i][j - 1][0], Max[f[i][j - 1]][j - 1][1]);
            else Max[i][j][1] = max (Max[i][j - 1][1], Max[f[i][j - 1]][j - 1][0]);
        }
}

3.贴代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
#define LL long long
#define MAXN 19
const LL INF = 9999999999999999;
struct node {
    int uu, vv;
    node (){}
    node (int U, int V){
        uu = U;
        vv = V;
    }
};
int n, m, u[300005], v[300005], fa[100005], r[300005], dep[100005];
LL ans, w[300005], da = INF, Max[100005][30][2], f[100005][30];
bool vis[100005], check[300005];
vector <node> G[100005];
void makeSet (int x){
    for (int i = 0; i <= x; i ++)
        fa[i] = i;
}
int findSet (int x){
    if (fa[x] != x)
        fa[x] = findSet (fa[x]);
    return fa[x];
}
void unionSet (int x, int y){
    for (int i = x; i <= y; i ++){
        int e = r[i], U = findSet (u[e]), V = findSet (v[e]);
        if (U != V){
            fa[U] = V;
            check[e] = 1;
            G[u[e]].push_back (node (v[e], w[e]));
            G[v[e]].push_back (node (u[e], w[e]));
            ans += w[e];
        }
    }
}
bool cmp (int x, int y){
    return w[x] < w[y];
}
void DFS (int x){
    for (int i = 0; i < G[x].size (); i ++){
        int tmp = G[x][i].uu, tot = G[x][i].vv;
        if (! vis[tmp]){
            vis[tmp] = 1;
            dep[tmp] = dep[x] + 1;
            f[tmp][0] = x;
            DFS (tmp);
            Max[tmp][0][0] = tot;
            Max[tmp][0][1] = -INF;
        }
    }
}
void prepare (){
    f[1][0] = 0;
    for (int j = 1; j <= MAXN; j ++)
        for (int i = 1; i <= n; i ++){
            f[i][j] = f[f[i][j - 1]][j - 1];
            Max[i][j][0] = max (Max[i][j - 1][0], Max[f[i][j - 1]][j - 1][0]);
            if (Max[i][j - 1][0] == Max[f[i][j - 1]][j - 1][0])
                Max[i][j][1] = max (Max[i][j - 1][1], Max[f[i][j - 1]][j - 1][1]);
            else if (Max[i][j - 1][0] < Max[f[i][j - 1]][j - 1][0])
                Max[i][j][1] = max (Max[i][j - 1][0], Max[f[i][j - 1]][j - 1][1]);
            else Max[i][j][1] = max (Max[i][j - 1][1], Max[f[i][j - 1]][j - 1][0]);
        }
}
void add (int &x, int maxdep){
    for (int i = MAXN; i >= 0; i --)
        if (dep[f[x][i]] >= maxdep)
            x = f[x][i];
}
int Doubly (int x, int y){
    if (dep[x] < dep[y])
        add (y, dep[x]);
    if (dep[x] > dep[y])
        add (x, dep[y]);
    if (x == y)
        return x;
    for (int i = MAXN; i >= 0; i --){
        if (f[x][i] != f[y][i]){
            x = f[x][i];
            y = f[y][i];
        }
    }
    return f[x][0];
}
int get_max (int x, int y, int k){
    int tx = x, ty = y;
    LL ans1 = 0, ans2 = 0;
    int LCA = Doubly (x, y);
    for (int i = MAXN; i >= 0; i --)
        if (dep[f[x][i]] >= dep[LCA]){
            ans1 = max (ans1, Max[x][i][0]);
            x = f[x][i];
        }
    for (int i = MAXN; i >= 0; i --)
        if (dep[f[y][i]] >= dep[LCA]){
            ans2 = max (ans2, Max[y][i][0]);
            y = f[y][i];
        }
    if (! k)
        return max (ans1, ans2);
    if (ans1 > ans2){
        ans1 = 0;
        x = tx;
        for (int i = MAXN; i >= 0; i --)
            if (dep[f[x][i]] >= dep[LCA]){
                ans1 = max (ans1, Max[x][i][1]);
                x = f[x][i];
            }
        return max (ans1, ans2);
    }
    else if (ans1 < ans2){
        ans2 = 0;
        y = ty;
        for (int i = MAXN; i >= 0; i --)
            if (dep[f[y][i]] >= dep[LCA]){
                ans2 = max (ans2, Max[y][i][1]);
                y = f[y][i];
            }
        return max (ans1, ans2);
    }
    else{
        x = tx, y = ty;
        ans1 = ans2 = 0;
        for (int i = MAXN; i >= 0; i --)
            if (dep[f[x][i]] >= dep[LCA]){
                ans1 = max (ans1, Max[x][i][1]);
                x = f[x][i];
            }
        for (int i = MAXN; i >= 0; i --)
            if (dep[f[y][i]] >= dep[LCA]){
                ans2 = max (ans2, Max[y][i][1]);
                y = f[y][i];
            }
        return max (ans1, ans2);
    }
}
int main (){
    scanf ("%d %d", &n, &m);
    int x, y, z;
    makeSet (n);
    for (int i = 1; i <= m; i ++){
        scanf ("%d %d %d", &x, &y, &z);
        u[i] = x, v[i] = y, w[i] = z;
    }
    for (int i = 1; i <= m; i ++)
        r[i] = i;
    sort (r + 1, r + 1 + m, cmp);
    unionSet (1, m);
    dep[1] = 1;
    vis[1] = 1;
    DFS (1);
    prepare ();
    for (int i = 1; i <= m; i ++){
        if (! check[i]){
            LL W = get_max (u[i], v[i], 0);
            if (w[i] == W)
                W = get_max (u[i], v[i], 1);
            da = min (da, ans - W + w[i]);
        }
    }
    printf ("%lld\n", da);
    return 0;
}

谢谢!

发布了61 篇原创文章 · 获赞 32 · 访问量 8359

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/90637574