有向图边连通分量(模板)
题意:给定有向图,问最少加多少边能使得图变为边连通分量
解法:tarjan缩点之后,整个图变为一颗树,答案就是叶子节点(度数为1)的一半。
const int N=1e5;
int n,m;int h[N],e[N],ne[N],idx;int is_bridge[N];int timestamp,dfn[N],low[N],dcc_cnt,id[N];int d[N],stk[N],top;
//点数、边数、邻接表、是否为桥、时间戳、dfs序号、最小dfs祖先、有向图连通分量数量、代表每个点属于哪个连通分量、度数、栈
void add(int a,int b)
{e[idx]=b,ne[idx]=h[a],h[a]=idx++;}
void tarjan(int u, int from)
{
dfn[u] = low[u] = ++ timestamp; stk[ ++ top] = u;
for (int i = h[u]; i!=-1; i = ne[i])
{
int j = e[i];
if (!dfn[j])//j未遍历过
{
tarjan(j, i);//dfs(j)
low[u] = min(low[u], low[j]);//用j更新u
if (dfn[u] < low[j])//j到不了u // 则x-y的边为桥,//正向边is_bridge[i] 反向边is_bridge[i ^ 1]都是桥
is_bridge[i] = is_bridge[i ^ 1] = true; // 判断反向边
}
// j遍历过 且i不是反向边(即i不是指向u的父节点的边) // 因为我们不能用u的父节点的时间戳更新u
else if (i != (from ^ 1))
low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
++ dcc_cnt; int y;
do { y = stk[top -- ]; id[y] = dcc_cnt; } while (y != u);
}
}
int main()
{
cin >> n >> m; memset(h, -1, sizeof h);
while (m -- )
{int a, b; cin >> a >> b; add(a, b), add(b, a);}
tarjan(1, -1);//防止搜反向边 用一个from
for (int i = 0; i < idx; i ++ ) //如果边i是桥 在其所连的出边的点j所在强连通分量的度+1
if (is_bridge[i]) d[id[e[i]]] ++ ;
int cnt = 0;
for (int i = 1; i <= dcc_cnt; i ++ )
if (d[i] == 1) cnt ++ ;
cout << (cnt + 1) / 2 << endl;
}