人生重开入口:395. 冗余路径 - AcWing题库
思路:题意就是给定一个图,问至少要加几条边才能把图变成一个边双连通分量,不含桥。
利用tarjan算法,缩完点后原图会变成一颗树(不是拓扑图),对于所有的叶节点,都至少要加一条边连向树中的其他节点。如果每次都连另一个叶子节点,那么最后要连的边的数目就是(cnt+1)/2,cnt是叶节点的数量。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=5e3+10,M=2e4+10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],times; //两个时间戳
int stk[N],top;
int id[N],dcc_cnt; //双连通分量编号
bool is_b[N]; //改边是否是桥
int d[N]; //记录该点度数
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u,int father)//记录来边,防止搜索反向边
{
dfn[u]=low[u]=++times;
stk[++top]=u;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!dfn[j])
{
tarjan(j,i);
low[u]=min(low[u],low[j]);
if(low[j]>dfn[u]) //若节点j永远走不到节点u,则为桥边
{
is_b[i]=is_b[i^1]=true; //反向边也是桥边
//加边时是两条边一起加的,所以同一路径的正向边和反向边总是同时出现。
//奇数异或1就是减1操作,偶数异或1就是加1操作
}
}else if(i!=(father^1)) //不能遍历回父节点。
low[u]=min(low[u],dfn[j]); //u的low值就是u不通过父节点能走到的节点的最小dfn值
}
if(dfn[u]==low[u]) //和强连通分量一样,当前节点能走到的最小时间戳是自己的话,则该点是边连通分量的最高点
{
++dcc_cnt;
int y;
do{
y=stk[top--];
id[y]=dcc_cnt;
}while(y!=u);
}
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
tarjan(1,-1);
for(int i=0;i<idx;i++) //idx记录的是边的数量,
{
if(is_b[i]) //边i是桥边的话,让它出边所在边连通分量度数加1,反向边加的是另一个方向的边连通分量的度数
{
d[id[e[i]]]++;
}
}
int sum=0;
for(int i=1;i<=dcc_cnt;i++)//遍历所有的边连通分量,度数为1说明是叶子节点。
if(d[i]==1)
sum++;
printf("%d\n",(sum+1)/2);
return 0;
}