【BZOJ1977】次小生成树 Tree

本文介绍了一种求解严格次小生成树的方法,利用最小生成树算法为基础,通过深度优先搜索确定路径上最大的两条边,从而找到次小生成树的边权和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                     1977: [BeiJing2010组队]次小生成树 Tree

                                            Time Limit: 10 Sec  Memory Limit: 512 MB
                                                       Submit: 3916  Solved: 1133

Description

小 C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 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 。

 

解析:

       我们知道,如果往最小生成树里加一条边一定能形成一个环,这时从环中选一个非新加入边的最大边权的边拿掉,就形成了一个次小生成树,于是解法就明显了。

       1.做一遍最小生成树并将用到的边建成一棵树。

       2.dfs求出d1[ i ][ j ],d2[ i ][ j ]分别表示从点 i 向上走 2^j 个单位沿途中第一、第二大的边权,因为题目说严格次小所以不能相等。

       3.对于每条不在最小生成树上的边,求出两点间的LCA,分别记录最大边权更新答案。

 

代码:
 

#include <bits/stdc++.h>
using namespace std;
 
const int Max=101000;
int n,m,size,s,minn=1e9;
long long ans;
int father[Max],f[Max][20],d1[Max][20],d2[Max][20],first[Max],depth[Max];
struct shu{int to,next,len;};
shu edge[Max*6];
struct tree{int x,y,len,v;};
tree Edge[Max*3];
 
inline int get_int()
{
    int x=0,f=1;
    char c;
    for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
    if(c=='-') {f=-1;c=getchar();}
    for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
    return x*f;
}
 
inline bool comp(const tree &a,const tree &b){return a.len < b.len;}
inline int getfather(int v){return father[v] == v ? v : father[v] =getfather(father[v]);}
inline void build(int x,int y,int z){edge[++size].next=first[x],first[x]=size,edge[size].to=y,edge[size].len=z;}
inline int mn(int x,int y){return x > y ? y : x;}
inline int mx(int x,int y){return x > y ? x : y;}
inline void dfs(int point,int fa)
{
    for(int i=1;i<=16;i++)
      if(depth[point] >= 1<<i)
      {
        f[point][i] = f[f[point][i-1]][i-1];
        d1[point][i] = mx(d1[point][i-1] , d1[f[point][i-1]][i-1]);
        if(d1[point][i-1] == d1[f[point][i-1]][i-1]) d2[point][i] = mx(d2[point][i-1] , d2[f[point][i-1]][i-1]);
        else
        {
          d2[point][i] = mn(d1[point][i-1] , d1[f[point][i-1]][i-1]);
          d2[point][i] = mx(d2[point][i] , d2[f[point][i-1]][i-1]);
        }
      }
      else break;
    for(int u=first[point];u;u=edge[u].next)
    {
      int to=edge[u].to;
      if(to == fa) continue;
      f[to][0] = point;
      depth[to] = depth[point] + 1;
      d1[to][0] = edge[u].len;
      dfs(to,point);
    }
}
 
inline int LCA(int x,int y)
{
    if(depth[x] < depth[y]) swap(x,y);
    int len = depth[x] - depth[y];
    for(int i=16;i>=0;i--)
      if(len >= 1<<i) len-=1<<i,x = f[x][i];
    if(x == y) return x;
    for(int i=16;i>=0;i--)
      if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
    return f[x][0];
}
 
inline void search(int x,int fa,int len)
{
    int max1=0,max2=0,h =depth[x] - depth[fa];
    for(int i=16;i>=0;i--)
      if(h >= 1<<i)
      {
        h-=1<<i;
        if(d1[x][i] > max1) max2 = max1,max1 = d1[x][i];
        max2 = mx(max2 , d2[x][i]);
        x=f[x][i];
      }
    if(len != max1) minn = mn(minn , len - max1);
    else minn = mn(minn , len - max2);
}
 
inline void solve(int x,int y,int z)
{
    int fa = LCA(x,y);
    search(x,fa,z),search(y,fa,z);
}
 
int main()
{
    n=get_int();
    m=get_int();
    for(int i=1;i<=n;i++) father[i] = i;
    for(int i=1;i<=m;i++) Edge[i].x=get_int(),Edge[i].y=get_int(),Edge[i].len=get_int();
 
    sort(Edge+1,Edge+m+1,comp);
    for(int i=1;i<=m;i++)
    {
      int fax = getfather(Edge[i].x),fay = getfather(Edge[i].y);
      if(fax != fay)
      {
        ans += Edge[i].len;
        father[fay] = fax;
        Edge[i].v = 1;
        build(Edge[i].x,Edge[i].y,Edge[i].len);
        build(Edge[i].y,Edge[i].x,Edge[i].len);
        s++;
        if(s == n-1) break;
      }
    }
 
    dfs(1,0);
    for(int i=1;i<=m;i++)
      if(!Edge[i].v) solve(Edge[i].x,Edge[i].y,Edge[i].len);
 
    cout<<ans+minn<<"\n";
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值