bzoj 1977 严格次小生成树(最小生成树 + LCA倍增)

题目地址

借鉴了大佬的博客:https://www.cnblogs.com/AK-ls/p/10509986.html

题目描述


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

思想概要


1. 先求出最小生成树
2. 然后枚举所有不在树上的边val加到树中,会形成一个环,在环找到最大边val1和次大边val2
    - 如果val > val1,则sum + val - val1为一个候选答案
    - 如果val = val1 ,则sum + val - val2为一个候选答案
3. 那么val1和val2要怎么求呢,就要用LCA倍增来维护 dis1[ i ][ j ]表示从 i 向上跳 2^j 次之间的最大边,dis2[ i ][ j ]表示从 i 向上跳 2^j 次之间的次大边(小于最大边)

fa[i][j] = fa[fa[i][j-1]][j-1];
dis1[i][j] = max(dis1[i][j-1], dis1[fa[i][j-1]][j-1]);        
if(dis1[i][j-1] == dis1[fa[i][j-1]][j-1])                        
    dis2[i][j] = max(dis2[i][j-1], dis2[fa[i][j-1]][j-1]);
else if(dis1[i][j-1] < dis1[fa[i][j-1]][j-1])
    dis2[i][j] = max(dis1[i][j-1], dis2[fa[i][j-1]][j-1]);
else
    dis2[i][j] = max(dis2[i][j-1], dis1[fa[i][j-1]][j-1]);    

注意:这题的数据很大,边权和要开long long。

AC代码

 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn = 5e5+10;
const int inf = 1e9+7;
int fa[maxn][22], pre[maxn], vis[maxn], deep[maxn], cnt,dis1[maxn][22], dis2[maxn][22];
int n, m, head[maxn];
struct Tree{
    int u, v, w;
}t[maxn];

struct Edge{
    int v, next, w;
}e[maxn*2];

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

int Find(int x){
    return x == pre[x] ? x:pre[x]=Find(pre[x]);
}

bool cmp(Tree a, Tree b){
    return a.w < b.w;
}

ll Kruskal(){    
    ll ans = 0;
    int num = 0;
    for(int i = 1; i <= m; i++){    
         int u = Find(t[i].u);
         int v = Find(t[i].v);
         if(u != v){
              pre[v] = u;
              ans += t[i].w*1ll;
              num++;
              add(t[i].u, t[i].v, t[i].w);
              add(t[i].v, t[i].u, t[i].w);
              vis[i] = 1;     
         }
         if(num == n-1)
            break;
    }
    return num == n-1?ans:-1;
}

void dfs(int u, int d){
    deep[u] = d;
    for(int i = head[u]; ~i; i = e[i].next){
        int v = e[i].v;
        if(fa[v][0]) continue;
        fa[v][0] = u;
        dis1[v][0] = e[i].w;
        dis2[v][0] = -inf;
        dfs(v, d+1);
    }
}

void update(){
    for(int j = 1;j <= 20; j++){
        for(int i = 1; i <= n; i++){
            fa[i][j] = fa[fa[i][j-1]][j-1];
            dis1[i][j] = max(dis1[i][j-1], dis1[fa[i][j-1]][j-1]);
            if(dis1[i][j-1] == dis1[fa[i][j-1]][j-1])
                dis2[i][j] = max(dis2[i][j-1], dis2[fa[i][j-1]][j-1]);
            else if(dis1[i][j-1] < dis1[fa[i][j-1]][j-1])
                dis2[i][j] = max(dis1[i][j-1], dis2[fa[i][j-1]][j-1]);
            else
                dis2[i][j] = max(dis2[i][j-1], dis1[fa[i][j-1]][j-1]);
        }
    }
}


int lca(int u, int v){
    if(deep[u] < deep[v])
        swap(u, v);
    int f = deep[u] - deep[v];
    for(int i = 0; (1<<i) <= f; i++){
        if(f & (1<<i)){
            u = fa[u][i];
        }
    }
    if(u != v){
        for(int i = 20; i >= 0; i--){
            if(fa[u][i] != fa[v][i]){
                u = fa[u][i];
                v = fa[v][i];
            }
        }
        u = fa[u][0];
    }
    return u;
}
int cal(int u, int f, int w){
    int v1, v2;
    v1 = v2 = -inf;
    int len = deep[u] - deep[f];
    for(int i = 0; (1<<i) <= len; i++){
        if(len & (1<<i)){
            v1 = max(dis1[u][i], v1);
            if(dis1[u][i] != v1)
                v2 = max(v2, dis1[u][i]);
            v2 = max(v2, dis2[u][i]);
            u = fa[u][i];
        }
    }
    if(v1 == w) return w - v2;
    else  return w - v1;
}
int main(){
    while(scanf("%d%d", &n, &m)!=EOF){
        int u, v, w;
        cnt = 0;
        memset(fa, 0, sizeof(fa));
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        for(int i = 1; i <= n; i++){
            pre[i] = i;
        }
        for(int i = 1; i <= m; i++){
            scanf("%d%d%d", &t[i].u, &t[i].v, &t[i].w);
        }
        sort(t+1, t+1+m, cmp);
        ll sum = Kruskal();
        fa[1][0] = 1;
        dfs(1, 1);
        update();
        int Min = inf;
        for(int i = 1; i <= m; i++){
            if(!vis[i]){
                int f = lca(t[i].u, t[i].v);
                Min = min(cal(t[i].u, f, t[i].w), Min);
                Min = min(cal(t[i].v, f, t[i].w), Min);
            }
        }
        printf("%lld\n", sum*1ll + Min*1ll);
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值