次小生成树 倍增 LCA

BZOJ 1977 次小生成树 倍增 LCA

给一个N个点M条边(N <= 100000, M <= 300000) 的无向图,求它的严格次小生成树。

数据范围很大,O(N^2)的算法显然是不行的。由于最小生成树是一棵树,求任意两点路径上的最大/次大边就成为了经典的LCA问题。因此,我们得到了下面的算法:1、把边按权值从小到大排序,时间复杂度为O(MlogM);2、用Kruskal算法求出原图的最小生成树(利用并查集),时间复杂度为O(Mα(N));3、以最小生成树的任意一点为根,用倍增法预处理出最小生成树上每个点往上第2^k层的祖先和到往上第2^k层的祖先路径上的最大边和次大边,时间复杂度为O(NlogN)(该过程必须BFS实现,因为DFS会爆栈);4、穷举每一条非最小生成树上的边(u, v),用这条边替换从u到v路径上的最大边(p, q),由于要严格次小,如果w(u, v) = w(p, q),则替换从u到v路径上的次大边(p', q'),对所有这样形成的新生成树取权值和最小的即为要求的严格次小生成树(从u到v路径上的最大/次大边利用倍增法求LCA的过程实现),时间复杂度为O(MlogN)。因此,由于N一般小于M,算法总体时间复杂度仍为O(MlogM)。

代码(由于一开始没有用long long,2Y)

#include <algorithm>
#include <cctype>
#include <climits>
#include <cstdio>

using namespace std;

#define f1 first
#define f2 second

typedef long long LL;
typedef pair<int, int> PII;

const int MAXN = 100001, MAXM = 300000;

struct edge
{
    int x, y, w;
} e[MAXM];

struct adj_list
{
    int v, w, nxt;
} a[MAXN << 1];

struct set
{
    int fa, s;
} s[MAXN];

bool v[MAXN], in[MAXM];
int n, m, size, h[MAXN], log2[MAXN], 
    fa[MAXN][20], dep[MAXN], q[MAXN];
LL mst;
PII d[MAXN][20];

inline bool operator < (edge x, edge y)
{
    return x.w < y.w;
}

inline void make_set(int x)
{
    s[x].fa = x;
    s[x].s = 1;
}

int find_set(int x)
{
    return s[x].fa == x ? x : s[x].fa = find_set(s[x].fa);
}

inline void union_set(int x, int y)
{
    if (s[x].s < s[y].s)
    {
        s[x].fa = y;
        s[y].s += s[x].s;
    }
    else
    {
        s[y].fa = x;
        s[x].s += s[y].s;
    }
}

inline void add_edge(int x, int y, int w)
{
    a[size].v = y;
    a[size].w = w;
    a[size].nxt = h[x];
    h[x] = size ++;
}

inline void check_max(int &x, int y)
{
    if (y > x)
        x = y;
}

inline void check_min(LL &x, LL y)
{
    if (y < x)
        x = y;
}

inline int getint()
{
    char c;
    while (!isdigit(c = getchar()));
    int ret = c - 48;
    while (isdigit(c = getchar()))
        ret = ret * 10 + c - 48;
    return ret;
}

inline void init()
{
    n = getint();
    m = getint();
    for (int i = 0; i < m; ++ i)
    {
        e[i].x = getint();
        e[i].y = getint();
        e[i].w = getint();
    }
    sort(e, e + m);
    for (int i = 1, j = 0; i <= n; ++ i)
    {
        make_set(i);
        h[i] = -1;
        log2[i] = j;
        if (i == (1 << j + 1) - 1)
            ++ j;
    }
}

inline void kruskal()
{
    for (int i = 0, c = 1; c < n; ++ i)
    {
        int fx = find_set(e[i].x), fy = find_set(e[i].y);
        if (fx != fy)
        {
            mst += e[i].w;
            ++ c;
            in[i] = true;
            add_edge(e[i].x, e[i].y, e[i].w);
            add_edge(e[i].y, e[i].x, e[i].w);
            union_set(fx, fy);
        }
    }
}

inline void bfs()
{
    q[0] = 1;
    v[1] = true;
    dep[1] = 0;
    for (int l = 0, r = 0; l <= r; ++ l)
    {
        int u = q[l];
        for (int k = 1; k <= log2[dep[u]]; ++ k)
        {
            fa[u][k] = fa[fa[u][k - 1]][k - 1];
            d[u][k].f1 = max(d[u][k - 1].f1, d[fa[u][k - 1]][k - 1].f1);
            d[u][k].f2 = max(d[u][k - 1].f1 != d[u][k].f1 ? d[u][k - 1].f1 : d[u][k - 1].f2,
                             d[fa[u][k - 1]][k - 1].f1 != d[u][k].f1 ? d[fa[u][k - 1]][k - 1].f1 : d[fa[u][k - 1]][k - 1].f2);
        }
        for (int k = h[u]; k != -1; k = a[k].nxt)
            if (!v[a[k].v])
            {
                v[a[k].v] = true;
                dep[a[k].v] = dep[u] + 1;
                fa[a[k].v][0] = u;
                d[a[k].v][0].f1 = a[k].w;
                q[++ r] = a[k].v;
            }
    }
}

inline PII max_edge(int x, int y)
{
    PII ret = make_pair(0, 0);
    if (dep[x] > dep[y])
        swap(x, y);
    while (dep[y] > dep[x])
    {
        int k = log2[dep[y] - dep[x]];
        check_max(ret.f1, d[y][k].f1);
        check_max(ret.f2, d[y][k].f1 != ret.f1 ? d[y][k].f1 : d[y][k].f2);
        y = fa[y][k];
    }
    if (x == y)
        return ret;
    for (int k = log2[dep[x]]; k >= 0; -- k)
        if (fa[x][k] != fa[y][k])
        {
            check_max(ret.f1, max(d[x][k].f1, d[y][k].f1));
            check_max(ret.f2, max(d[x][k].f1 != ret.f1 ? d[x][k].f1 : d[x][k].f2,
                                  d[y][k].f1 != ret.f1 ? d[y][k].f1 : d[y][k].f2));
            x = fa[x][k];
            y = fa[y][k];
        }
    check_max(ret.f1, max(d[x][0].f1, d[y][0].f1));
    check_max(ret.f2, max(d[x][0].f1 != ret.f1 ? d[x][0].f1 : d[x][0].f2,
                          d[y][0].f1 != ret.f1 ? d[y][0].f1 : d[y][0].f2));
    return ret;
}

inline void work()
{
    LL ans = LONG_LONG_MAX;
    for (int i = 0; i < m; ++ i)
        if (!in[i])
        {
            PII t = max_edge(e[i].x, e[i].y);
            check_min(ans, mst - (t.f1 == e[i].w ? t.f2 : t.f1) + e[i].w);
        }
    printf("%lld\n", ans);
}

int main()
{
    init();
    kruskal();
    bfs();
    work();
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值