牛客练习赛32D——Where are you(最小生成树+无向图的桥)

题目链接:https://ac.nowcoder.com/acm/contest/272/D

思路和代码参考于:https://blog.csdn.net/qq_37025443/article/details/84747158

看着官方答案也写不出来,只好参考了大神的代码,还是我自己太菜了。

仔细看题意和样例可以知道,如果没有权值相同的边,那么答案明显就是最小生成树中桥的个数,克鲁斯卡尔+Tarjan就可以解决;当有相同权值边时,上面方法就不好处理了,最小生成树不唯一。所以,对于所有最小生成树上的边,我们都加到一个新图中去,如果有重边,在加入新图中的同时,以重边的一个顶点为起点,Tarjan一遍我们的新图(对于每个重边,如果顶点属于不同的连通分量,都要Tarjan),标记所有为桥的边,并去除新图。最后,被标记为桥的边就一定是在最小生成树上的边。

细节见代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+100;
const int INF=1<<30;

struct edge{
    int fr,to,nxt;
    ll w;
    int id;
    bool operator < (const edge &a) const {
        return w<a.w;
    }
};
int pre[N],n,head[N],cnt;
edge e[N*2];
edge f[N*2];
bool is_cut[N];
int father[N];
int low[N],dfn[N];
int tim=0;
int ans[N];
int visit[N];

void add1(int fr,int to,ll w){
    e[cnt].fr=fr;
    e[cnt].to=to;
    e[cnt].w=w;
    cnt++;
}
int fcnt;
void add2(int fr,int to,int id){
    f[fcnt].fr=fr;
    f[fcnt].to=to;
    f[fcnt].id=id;
    f[fcnt].nxt=head[fr];
    head[fr]=fcnt++;
}

int fin(int x){
    if(x==pre[x])
        return x;
    return pre[x]=fin(pre[x]);
}
//tarjan算法寻找桥
void tarjan(int x, int fa) {
    dfn[x] = low[x] = ++tim;
    for(int i = head[x], to, id; i!=-1; i=f[i].nxt) {
        if((id = f[i].id) == fa) continue;
        to = f[i].to;
        if(!dfn[to]) {
            tarjan(to, id), low[x] = min(low[x], low[to]);
            if(low[to] > dfn[x]) ans[id] = 1;
        }
        else low[x] = min(low[x], dfn[to]);
    }
}

/*
    如果没有重边,答案就是最小生成树的边的个数(所有的边都是桥)
    如果有重边,将所有的边(当然是最小生成树的重边)都加到新图中去,tarjan求桥,求出来的
    桥的个数就是答案
*/

void Kruskal(){
    fcnt=0;
    memset(head,-1,sizeof(head));
    //贪心+并查集
    for(int i=1;i<=n;++i)
        pre[i]=i;
    sort(e,e+cnt);
    for(int i=0;i<cnt;++i){

        int u=fin(e[i].fr);
        int v=fin(e[i].to);
        if(u!=v){
            //在新图中加入最小生成树的边
            add2(u,v,i);
            add2(v,u,i);
        }
        int j=i+1;
        //有边权相同的边
        while(j<cnt&&e[j-1].w==e[j].w)
        {
            u=fin(e[j].fr);
            v=fin(e[j].to);
            if(u!=v){
                //如果不是一个联通分量的,加入新图,满足克鲁斯卡尔的思想
                add2(u,v,j);
                add2(v,u,j);
            }
            j++;
        }
        for(int w=i;w<j;w++)
        {
            u=fin(e[w].fr);
            v=fin(e[w].to);
            //寻找桥
            if(u!=v&&!dfn[u]){
                tarjan(u,-1);
            }
        }
        //删除新图,以免影响之后的判断
        for(;i<j;i++)
        {
            u=fin(e[i].fr);
            v=fin(e[i].to);
            if(u!=v){
                dfn[u]=dfn[v]=0;
                low[u]=low[v]=0;
                head[u]=head[v]=-1;
                pre[v]=u;
            }
        }
        fcnt=0;
        i--;
    }
}

int p;
void _count()
{

    ll res=0;
    for(int i=0;i<cnt;i++)
    {
        if(ans[i]) res++;
    }
    printf("%lld\n",res);

}

int main(){
    int a,b,c;
    int m;
    scanf("%d%d%d",&n,&m,&p);

        while(m--){
            scanf("%d%d%d",&a,&b,&c);
            add1(a,b,c);
        }
        Kruskal();
        memset(dfn,0,sizeof(dfn));
        memset(father,0,sizeof(father));
        memset(low,0,sizeof(low));
        memset(is_cut,false,sizeof(is_cut));
        tim=0;
        _count();
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值