题目
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;
}