次小生成树

之前学过最小生成树,后来做题时遇到一些题目需要用次小生成树的方法去求解,看了一些大牛的讲解后,大概讲一下自己的理解。

次小生成树的定义就是所有生成树中权值仅大于MST的(假设MST只有一棵如果有两棵那我们随便钦定一棵树就可以了)。

现在有一些可以利用的结论:

1、在给定的图中,次小生成树至少有一条边不是MST的边。

  证明:比较显然,具体我也不会……

2、次小生成树是给定的图中某条不在MST中的边对应的最小生成树。

证明:也比较显然,具体我也不会……

3、所求的次小生成树除了2中所说的那条不在MST中的边,其他的边其实都在MST中。

证明:这里不太明显,但是我还是不会……不过利用一下反证法好像可以说得通。假设有次小生成树T1  v1 v2 v3 v4四个点属于T1,v1,v2是用不在MST中的边连接起来的点,且v3,v4也不是用MST中的边连接起来,那么V3和V4若用MST的边连接起来形成的生成树则比T1的权值更小且该树不是MST。

经过上面证明我们大概就知道其实次小生成树就是MST删掉一条边,然后形成两个连通块,再用图中某条边把这两个连通块连接起来,就是次小生成树了。

算法过程大概是

1、枚举图中不在MST的边

2、然后只加入这条边的基础上,我们对这个图跑一次Kruskal或者是prim,

3、实际上2中我们prim或者Kruskal需要用到的边只有MST中的边。

复杂度是o(v * e)

有大佬的方法是

1、枚举不在MST的边,假设连接u,v

2、在MST中加入这条边,(实际上相当于u,v成了一个环)

3、删除掉MST中u,v这条路径上最长的边。

3中的方法可以用树链剖分来实现,貌似LCA也可以,复杂度是log v

总的复杂度是o(e *logv ) 

可惜的是,我不会……

贴一下方法1的代码,方法2尚在学习中

#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<time.h>
#include<cstdio>
#include<vector>
#include<stack>
#include<queue>
#include<iostream>
using namespace std;
#define  LONG long long
const LONG   INF=0x3f3f3f3f;
const LONG MOD=1e9+7;
const double PI=acos(-1.0);
#define clrI(x) memset(x,-1,sizeof(x))
#define clr0(x) memset(x,0,sizeof x)
#define clr1(x) memset(x,INF,sizeof x)
#define clr2(x) memset(x,-INF,sizeof x)
#define EPS 1e-10
struct Edge
{
    int u ,v ,w;
}edge[20000];
int fa[2000];
int found(int x)
{
    if(x == fa[x])return x;
    else return fa[x] = found(fa[x]);
}
bool cmp(Edge a, Edge b){
    return a.w < b.w;
}
int tot = 0;
struct Mst_edge{
    int u ,v ,w;
}mst_edge[20000];
bool use[20000];
int head[2000];
void add(int u , int v, int w)
{
    mst_edge[++tot].u =  u;
    mst_edge[tot].v = v;
    mst_edge[tot].w = w;

}
void Init()
{
    tot = 0;
    clr0(use);
    for(int i = 1 ;i <= 1500; ++ i)fa[i] = i;

}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        Init();
        int n , m ;
        scanf("%d%d",&n,&m);

        for(int i = 1; i<= m ;++ i)
            scanf("%d%d%d",&edge[i].u,&edge[i].v , &edge[i].w);
        sort(edge+1 ,edge + m + 1, cmp);
        int sum_mst = 0;
        for(int i = 1; i<= m ; ++ i)
        {
            int u = edge[i].u;
            int v = edge[i].v;
            int w = edge[i].w;
            int r1 = found(edge[i].u);
            int r2 = found(edge[i].v);
            if(r1 != r2)
            {
                use[i] = 1;
                fa[r2] = r1;
                fa[edge[i].u] = r1;
                fa[edge[i].v] = r1;
                sum_mst += edge[i].w ;
                add(u , v , w) ;
            }

        }
//        cout<<sum_mst<<endl;
//        cout<<tot<<endl;
        int ans = INF ;
        int sum_nst = 0;
        for(int i = 1 ;i <= m ;++ i)
        {
            sum_nst = 0;
            if(use[i]) continue ;
            for(int j = 1; j<= n ;++ j)fa[j] = j;
            int u = edge[i].u ;
            int v = edge[i].v ;
            fa[u] = fa[v ] ;
            sum_nst += edge[i].w ;
            for(int j = 1; j <= tot  ;++ j)
            {
                int u = mst_edge[j].u;
                int v = mst_edge[j].v;
                int w = mst_edge[j].w ;
                int r1 = found(u);
                int r2 = found(v);
                if(r1 != r2)
                {
                    fa[r1] = r2;
                    fa[u] = r2;
                    fa[v] = r2;
                    sum_nst += w;
                }
            }
            ans = min(ans , sum_nst);
        }
        if(ans == sum_mst)
            printf("Not Unique!\n");
        else cout<<sum_mst<<endl;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值