【模板】严格次小生成树[BJWC2010] --- kruskal重构树 + LCA

5 篇文章 0 订阅

传送门:洛谷4180


题目大意

给出 n n n个点, m m m条边的无向图,求严格次小生成树.
  即保证 次小生成树的边权和 > > >最小生成树的边权和


分析

首先提供一条定理:次小生成树一定由最小生成树经过”边交换”(加上一条边再删去一条边)得到.
  因此考虑先用 kruskal求出一颗 M S T MST MST,然后枚举剩下的边,依次加入再删去 M S T MST MST上一条边(保证这条边的权值严格小于加入边权值,且最大)
  问题的关键就在于如何求出要删去的边.
   → \to 首先忽略掉"严格小于"这一条件的话,就只要求 M S T MST MST上两点间路径上的最大值,对此可以考虑 用kruskal 重构树来简化一下(普通的LCA也行,只不过还要再扫一遍)
   → \to M S T MST MST不唯一的话,还是求得 L C A LCA LCA后再扫一遍吧(在kruskal重构树上,两叶子节点之间的点权,即为路径上的边权)

代码

洛谷上开 O 2 O_2 O2 388 m s 388ms 388ms,暂时 r a n k 1 rank1 rank1

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

#define IL inline

using namespace std;
typedef long long ll;

IL int read()
{
    int sum = 0;
    bool k = 1;
    char c = getchar();
    for(;'0' > c || c > '9'; c = getchar())
    if(c == '-') k = 0;
    for(;'0' <= c && c <= '9'; c = getchar())
        sum = sum * 10 + (c - '0');
    return k ? sum : -sum;
}

int n, m;

struct Side
{
    int u, v, w;
    bool f;
    bool operator < (const Side &b) const
    {	
        return w < b.w;
    }
}side[300005];

int fa[200005][18];
int dep[200005];
int weight[200005];
int lg[200005];

int pa[200005];
IL int find(int x) { return pa[x] = (pa[x] == x ? x : find(pa[x])); }
IL bool join(int x, int y, int z, int &T)
{
    int x1 = find(x), y1 = find(y);
    if(x1 == y1) return 0;
    ++T;
    fa[x1][0] = fa[y1][0] = T + n;
    weight[T + n] = z;
    
    pa[x1] = pa[y1] = T + n;
    
    return 1;
}

IL ll kru()
{
    sort(side + 1, side + m + 1);
    
    for(int i = 1; i <= n; ++i)
    {
        pa[i] = i; pa[i + n] = i + n;
    }
    
    ll sum = 0;
    int T = 0;
    for(int i = 1; i <= m && T < n; ++i)
    if(join(side[i].u, side[i].v, side[i].w, T))
    {
        side[i].f = 1;
        sum += side[i].w;
    }
    return sum;
}

IL int get_dep(int u)
{
    if(dep[u] != -1) return dep[u];
    if(!fa[u][0]) return dep[u] = 0;
    return dep[u] = get_dep(fa[u][0]) + 1;
}

IL void lca()
{
    int n2 = (n << 1) - 1;
    memset(dep, -1, sizeof(dep));
    for(int i = 1; i <= n2; ++i)
    if(dep[i] == -1)
        get_dep(i);
    for(int i = 2; i <= n2; ++i)
        lg[i] = lg[i >> 1] + 1;
    for(int j = 1; j <= lg[n2]; ++j)
    for(int i = 1; i <= n2; ++i)
    if(fa[i][j - 1])
        fa[i][j] = fa[fa[i][j - 1]][j - 1];
}

IL void swap_(int &x, int &y) { int tmp = x; x = y; y = tmp; }

IL int query(int x, int y)
{
    if(dep[x] < dep[y]) swap_(x, y);
    for(int t = dep[x] - dep[y]; t; t -= t & (-t))
        x = fa[x][lg[t & (-t)]];
    if(x == y) return x;
    for(int i = lg[dep[x]]; i >= 0; --i)
    if(fa[x][i] != fa[y][i])
    {
        x = fa[x][i];
        y = fa[y][i];
    }
    return fa[x][0];
}

IL void solve(ll sum)
{
    ll det = -1, w;
    for(int i = 1, p, x, y; i <= m; ++i)
    if(!side[i].f)
    {
        x = side[i].u; y = side[i].v;
        p = query(x, y);
        w = side[i].w - weight[p];
        if(w)
        {
            if(det == -1 || w < det) det = w;
        }else
        if(!w)
        {
            for(x = fa[x][0]; x != p; x = fa[x][0])
            if(weight[x] != side[i].w && weight[x] > w)
                w = weight[x];
            for(y = fa[y][0]; y != p; y = fa[y][0])
            if(weight[y] != side[i].w && weight[y] > w)
                w = weight[y];
            det = weight[p] - w;
        }
    }
    printf("%lld\n", det + sum);
}

int main()
{
    n = read(); m = read();
    for(int i = 1; i <= m; ++i)
    {
        side[i].u = read(); side[i].v = read(); side[i].w = read();
    }
    ll sum = kru();
    lca();
    solve(sum);
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值