参考博客:
jumping_frog
poj 3177 Redundant Paths(边双连通分量+缩点)
链接:http://poj.org/problem?id=3177
题意:有n个牧场,Bessie 要从一个牧场到另一个牧场,要求至少要有2条独立的路可以走。现已有m条路,求至少要新建多少条路,使得任何两个牧场之间至少有两条独立的路。两条独立的路是指:没有公共边的路,但可以经过同一个中间顶点。
分析:在同一个边双连通分量中,任意两点都有至少两条独立路可达,所以同一个边双连通分量里的所有点可以看做同一个点。
缩点后,新图是一棵树,树的边就是原无向图的桥。
现在问题转化为:在树中至少添加多少条边能使图变为双连通图。
结论:添加边数=(树中度为1的节点数+1)/2
具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
其实求边双连通分量和求强连通分量差不多,每次访问点的时候将其入栈,当low[u]==dfn[u]时就说明找到了一个连通的块,则栈内的所有点都属于同一个边双连通分量,因为无向图要见反向边,所以在求边双连通分量的时候,遇到反向边跳过就行了。
网上有一种错误的做法是:因为每一个双连通分量内的点low[]值都是相同的,则dfs()时,对于一条边(u,v),只需low[u]=min(low[u],low[v]),这样就不用缩点,最后求度数的时候,再对于每条边(u,v)判断low[u]是否等于low[v],若low[u]!=low[v],则不是同一个边双连通分量,度数+1即可…..
咋看之下是正确的,但是这种做法只是考虑了每一个强连通分量重只有一个环的情况,如果有多个环,则会出错。
比如这组数据:
11 14
1 7
7 11
7 8
8 10
8 9
9 11
9 10
1 2
2 6
2 5
5 3
5 4
4 6
4 3
画出来图如下(括号里是dfn值):
由于一个边双联通分量里出现了两个环所以最后点10的low值是4而不是2,所以该方法有错。
代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 5005;
int map[1000][1000];
bool instack[maxn];
int stack[maxn],low[maxn],dfn[maxn],color[maxn],degree[maxn];
int n, m, sum, top, cnt;
vector <int> G[maxn];
void tarjan(int u,int father)
{
low[u] = dfn[u] = ++cnt;
stack[++top] = u;
instack[u] = true;
int v;
for (int i = 0; i < G[u].size(); i++)
{
v = G[u][i];
if (!dfn[v])
{
tarjan(v,u);
low[u] = min(low[u],low[v]);
}
else if (v != father && instack[v])
low[u] = min(low[u],dfn[v]);
}
if (dfn[u] == low[u])//同一边双联通量进行缩点
{
sum++;
do
{
v = stack[top--];
instack[v] = false;
color[v] = sum;
}while (v != u);
}
}
void init()
{
memset(map,0,sizeof(map));
memset(dfn,0,sizeof(dfn));
memset(instack,0,sizeof(instack));
memset(degree,0,sizeof(degree));
for (int i = 0; i < maxn; i++)
G[i].clear();
cnt = sum = top = 0;
}
int main()
{
int u, v;
while (scanf("%d%d",&n,&m) != EOF)
{
init();
for (int i = 0; i < m; i++)
{
scanf("%d%d",&u,&v);
if (map[u][v]) continue;
G[u].push_back(v);
G[v].push_back(u);
map[u][v] = map[v][u] = 1;//这题要去重边,找bug找半天,我***
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i,-1);
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < G[i].size(); j++)
{
v = G[i][j];
if (color[i] != color[v])
degree[color[v]]++;
}
}
int ans = 0;
for (int i = 1; i <= sum; i++)
if (degree[i] == 1)
ans++;
printf("%d\n",(ans+1)/2);
}
return 0;
}