牛客小白月赛12 I-华华和月月逛公园(强连通分量-tarjan判桥/割边)

题目

N个点,M条边的无向图,

遍历这张图,有些边必须要走

现在问不必要走的边的条数

N<=1e5,M<=3e6

思路来源

https://www.cnblogs.com/nullzx/p/7968110.html(割点与割边)

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40397318(代码思路来源)

题解

割边是必须要走的,

反证法,如果不走割边,则有两个及以上的连通分量,且无法从一个到另一个

所以割边必走,才能遍历所有连通分量

所以答案就是所有边减去割边的数量

大概就是tarjan割边板子题,然而之前并没学过,补了一下

low[v]>dfn[u]的边u->v是割边,其中在dfs序中,由u遍历到v

桥的数组和边的数组开一样大就可以

思路来源的代码非常的巧,

为了照顾i与i^1的一致,令++cnt从2开始,2和3满足这个关系

dfs记录当前节点和前驱边,

如果后继边不是前驱边的反向边,

说明从另一条边戳到了以前的祖先,用dfn[v]更新low[u]

否则,应该是回溯的过程,用low[v]更新dfn[u]

tarjan的代码还算非常短,是一种基于dfs的实现,

今晚,算是真正理解了,终于可以手敲了

代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e5+10;
const int maxm=6e5+10;
int n,m,head[maxn];
int cnt=1;
int dfn[maxn],low[maxn],num;
bool bridge[maxm];
int ans,u,v;
struct edge
{
    int to,next;
}e[maxm];
void add(int u,int v)
{
    e[++cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int u,int in)
{
    low[u]=dfn[u]=++num;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!dfn[v])
        {
            dfs(v,i);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])
            bridge[i]=bridge[i^1]=1;
        }
        else if(i!=(in^1))
        low[u]=min(low[u],dfn[v]);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    for(int i=1;i<=n;++i)
    if(!dfn[i])dfs(i,0);
    for(int i=2;i<cnt;i+=2)
    if(bridge[i])ans++;//bridge 
    printf("%d\n",m-ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/89036674