【BZOJ1977】【MST】【LCA】[BeiJing2010组队]次小生成树 Tree 题解

5 篇文章 0 订阅
2 篇文章 0 订阅

Description

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

Input

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

Output

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

Sample Input

5 6

1 2 1

1 3 2

2 4 3

3 5 4

3 4 3

4 5 6
Sample Output

11
HINT

数据中无向图无自环; 50% 的数据N≤2 000 M≤3 000; 80% 的数据N≤50 000 M≤100 000; 100% 的数据N≤100 000 M≤300 000 ,边权值非负且不超过 10^9 。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <set>
#include <queue>
#include <algorithm>
#include <vector>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <stack>
#define INF 2147483647
#define LL long long
#define clr(x) memset(x, 0, sizeof x)
#define ms(a, x) memset(x, a, sizeof x)
#define digit (ch <  '0' || ch >  '9')
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif

using namespace std;

template <class T> inline void read(T &x) {
    int flag = 1; x = 0;
    char ch = getchar();
    while(ch <  '0' || ch >  '9') { if(ch == '-')  flag = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9') { x = (x<<1)+(x<<3)+ch-'0'; ch = getchar(); }
    x *= flag;
}

using namespace std;

const int maxn = 100001;
const int maxm = 300001;
int n,m,tot,cnt,mn = INF;
LL ans;
int f[maxn],head[maxn],deep[maxn],fa[maxn][17],d1[maxn][17],d2[maxn][17];
struct data{ int x,y,v; bool sel; } a[maxm];
struct edge{ int to,nxt,v; } e[maxn<<1];

inline bool cmp(data a, data b) { return a.v < b.v; }

inline void add(int u, int v, int w) { e[++cnt].to = v; e[cnt].nxt = head[u]; e[cnt].v = w; head[u] = cnt; }

inline void insert(int u,int v,int w) { add(u, v, w); add(v, u, w); }

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

void dfs(int x, int f) {
    for(int i = 1; i <= 16; i++) {
        if(deep[x] < (1<<i)) break;
        fa[x][i] = fa[fa[x][i-1]][i-1];
        d1[x][i] = max(d1[x][i-1], d1[fa[x][i-1]][i-1]);
        if(d1[x][i-1] == d1[fa[x][i-1]][i-1])
            d2[x][i] = max(d2[x][i-1], d2[fa[x][i-1]][i-1]);
        else {
            d2[x][i] = min(d1[x][i-1], d1[fa[x][i-1]][i-1]);
            d2[x][i] = max(d2[x][i-1], d2[x][i]);
            d2[x][i] = max(d2[x][i], d2[fa[x][i-1]][i-1]);
        }
    }
    for(int i = head[x]; i; i = e[i].nxt) if(e[i].to != f) {
        fa[e[i].to][0] = x;
        d1[e[i].to][0] = e[i].v;
        deep[e[i].to] = deep[x]+1;
        dfs(e[i].to, x);
    }
}

int lca(int x, int y) {
    if(deep[x] < deep[y]) swap(x, y);
    int t = deep[x]-deep[y];
    for(int i = 0; i <= 16; i++) if((1<<i)&t) x = fa[x][i];
    for(int i = 16; i >= 0; i--) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
    return x == y ? x : fa[x][0];
}

inline void cal(int x, int f, int v) {
    int mx1 = 0, mx2 = 0, t = deep[x]-deep[f];
    for(int i = 0; i <= 16; i++) if(t&(1<<i)) {
       if(d1[x][i] > mx1) mx2 = mx1, mx1 = d1[x][i];
       mx2 = max(mx2, d2[x][i]); x = fa[x][i];
    }
    mn = min(mn, mx1 != v ? v-mx1 : v-mx2);
}

int main() {
    read(n); read(m);
    for(int i = 1; i <= n; i++) f[i] = i;
    for(int i = 1; i <= m; i++) read(a[i].x), read(a[i].y), read(a[i].v);
    sort(a+1, a+m+1, cmp);
    for(int i = 1; i <= m; i++) {
        int p = find(a[i]. x), q = find(a[i].y);
        if(p != q) {
            f[p] = q;
            ans += a[i].v;
            a[i].sel = 1;
            insert(a[i].x, a[i].y, a[i].v);
            tot++;
            if(tot == n-1) break;
        }
    }
    dfs(1, 0);
    for(int i = 1; i <= m; i++) if(!a[i].sel)
        cal(a[i].x, lca(a[i].x, a[i].y), a[i].v), cal(a[i].y, lca(a[i].x, a[i].y), a[i].v);
    printf(AUTO, ans+mn);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值