BZOJ - 1977 [BeiJing2010组队]次小生成树 Tree Kruskal演算法+最近公共祖先

25 篇文章 0 订阅
2 篇文章 0 订阅

【Description】
小C最近学了很多最小生成树的算法,Prim算法、Kruskal算法、消圈算法 等等。正当小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。

思路:先用Kruskal算法生成最小生成树,得到权值和sum,并标记在树中的边。然后枚举不在树中的边,加上任意一条边(u,v)后,最小生成树就会形成一条回路。在这条回路上找一条小于(u,v)权值的最大边,将它删除,就生成一颗次小生成树,则新生成树的权值和tmp=sum-t+w(u,v).最后将所有的tmp比较找到最小的极为严格次小生成树的权值和。

由此我们得到“严格次小生成树”的算法流程:
Step1、得到图的最小生成树,并得到权值和为sum;

Step2、枚举不是最小生成树的边(u,v),在最小生成树中查找u->v路径上小于 w(u,v)的最大边t(最近公共祖先),则新生成树的权值和tmp=sum-t+w(u,v);

Step3、从所有的tmp中选择一个最小的为严格次小生成树的权值和 ans=min(ans,tmp);

/*
    Name: Tree
    Copyright: Twitter & Instagram @stevebieberjr
    Author: @stevebieberjr
    Date: 12-07-16 15:02
*/
#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#define LL long long
#define maxn 300005
using namespace std;

int N,M,pa[maxn];

bool vis[maxn],used[maxn];
int dep[maxn],dist[maxn],fa[maxn]; //注意fa数组与pa数组有所区分
vector<int>g[maxn],w[maxn];

void DFS(int i) //DFS得到最小生成树各节点的fa[i]父亲结点,dep[i]深度,dist[i]到根结点的距离
{
    vis[i]=true;
    for(int k=0;k<g[i].size();k++)
    {
        int j=g[i][k],c=w[i][k];
        if(vis[j]) continue;
        fa[j]=i;
        dep[j]=dep[i]+1;
        dist[j]=dist[i]+c;
        DFS(j);
    }
}

struct edge
{
    long long u,v,w;
};

vector<edge>E; //结构体+变长数组存储无向图的边

bool cmp(edge a,edge b)
{
    return a.w<b.w;
} //根据边的权值由小到大排序

void initial(int n)
{
    for(int i=0;i<=n;i++)
    {
        pa[i]=i;
    }
} //将图清为空边图

int find(int i)
{
    if(pa[i]==i) return i;
    int root=find(pa[i]);
    pa[i]=root;
    return root;
} //找i结点的根结点

void Union(int x, int y)
{
    int px=find(x);
    int py=find(y);
    pa[px]=py;
} //并集

bool judge(int x,int y)
{
    int px=find(x);
    int py=find(y);
    return px==py;
} //检查x结点与y结点是否在同一个集合里

long long kruskal()
{
    sort(E.begin(),E.end(),cmp);
    initial(N);
    LL sum=0;
    for(int k=0;k<E.size();k++)
    {
        int x=E[k].u,y=E[k].v;
        if(judge(x,y)) continue;
        Union(x,y);
        sum+=E[k].w;
        g[x].push_back(y);
        g[y].push_back(x);
        w[x].push_back(E[k].w);
        w[y].push_back(E[k].w);  //树的存储结构
        used[k]=1; //将在最小生成树中的边进行标记
    }
    return sum;
} //Kruskal演算法找最小生成树

int getmax(int x,int y,int z)
{
    int maxv=0;
    if(dep[x]<dep[y]) swap(x,y);
    while(dep[x]>dep[y])
    {
        if(dist[x]-dist[fa[x]]<z)
        {
            maxv=max(maxv,dist[x]-dist[fa[x]]);
        }
        x=fa[x];
    }
    while(x!=y)
    {
        if(dist[x]-dist[fa[x]]<z)
        {
            maxv=max(maxv,dist[x]-dist[fa[x]]);
        }
        if(dist[y]-dist[fa[y]]<z)
        {
            maxv=max(maxv,dist[y]-dist[fa[y]]);
        }
        x=fa[x];
        y=fa[y];
    }
    return maxv;
} //找x,y到最近公共祖先上的小于增加的边的权值的最大边

void solve()
{
    LL sum=kruskal(); //得到最小生成树
    DFS(1); //生成最小生成树的各种数组
    LL ans=(long long)100000*1000000000;
    for(int k=0;k<E.size();k++)
    {
        if(used[k]) continue; //如果是最小生成树的边,则忽略
        int u=E[k].u,v=E[k].v;
        int t=getmax(u,v,E[k].w); //计算LCA(u,v)同时计算u->v路径上小于e[k].w的最大边权
        if(t<0) continue; //如果没找到,则忽略
        ans=min(ans,sum-t+E[k].w); //计算新树的权值和sum-t+E[k].w
    }
    cout<<ans<<endl;
}

int main()
{
    //freopen("in.txt","r",stdin);
    scanf("%d%d",&N,&M);
    int x,y,z;
    for(int i=1;i<=M;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        E.push_back((edge){x,y,z});
    }
    solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值