题目来源:http://poj.org/problem?id=3352
引用一下解题思路:http://blog.csdn.net/geniusluzh/article/details/6619575
这道题的意思是说,给你一个无向图,然后问你至少需要添加几条边,可以使整个图变成边双连通分量,也就是说任意两点至少有两条路可以互相连通。我们这样考虑这个问题,属于同一个边双连通分量的任意点是至少有两条通路是可以互相可达的,因此我们可以将一个边双连通分量缩成一个点。然后考虑不在边双连通分量中的点,通过缩点后形成的是一棵树。对于一个树型的无向图,需要添加(度为1的点的个数+1)/2条边使得图成为双连通的。这样问题就是变成缩点之后,求图中度为1的点的个数了。
图是无向图,跑Tarjan的时候把无向图当成有向图跑就行了(就是不走回头路)
做缩点的题的时候一个比较容易犯的马虎错是把原图的点和新图的点混淆,所以类似degree[bel[ver[i]]]写成degree[ver[i]]的问题一定要注意。
另外写邻接表时有时会把当前结点和子节点混淆(就是把ver[i]写成了now),这也要注意。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=2000,M=2000;
int dfn[N],low[N],t=0;
int head[N],nxt[M<<1],ver[M<<1],tot=1;
bool danger[M<<1];
int degree[N],bel[N],scc=0,leaf=0;
int n,m;int col[N];
void add (int u,int v) {
ver[++tot]=v;nxt[tot]=head[u];head[u]=tot;
}
void Tarjan (int x,int fa) {
dfn[x]=low[x]=++t;col[x]=-1;
for (int i=head[x];i;i=nxt[i]) {
if (ver[i]==fa) continue;
if (!dfn[ver[i]]) {
Tarjan(ver[i],x);
low[x]=min(low[x],low[ver[i]]);
if (dfn[x]<low[ver[i]])
danger[i]=danger[i^1]=1;//not node
}
else if (col[ver[i]]==-1) low[x]=min(low[x],dfn[ver[i]]);
}
col[x]=1;
}
void dfs (int x) {
bel[x]=scc;
for (int i=head[x];i;i=nxt[i])
if (!bel[ver[i]]&&!danger[i]) dfs(ver[i]);//bel[ver[i]] instead of bel[x],this is important
}
int solve () {
for (int i=1;i<=n;i++) {
if (bel[i]) continue;
++scc;dfs(i);
}
for (int u=1;u<=n;u++) {
for (int i=head[u];i;i=nxt[i])
if (bel[u]!=bel[ver[i]]) ++degree[bel[ver[i]]];
}
for (int i=1;i<=scc;i++) {
if (degree[i]==1) ++leaf;
}
return ((leaf+1)>>1);
}
int main () {
int u,v;
while (scanf("%d %d",&n,&m)!=EOF) {
memset(head,0,sizeof(head));
memset(degree,0,sizeof(degree));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(danger,0,sizeof(danger));
memset(bel,0,sizeof(bel));
memset(col,0,sizeof(col));
t=0,tot=1,scc=0,leaf=0;
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]) Tarjan(i,0);
printf("%d\n",solve());
}
return 0;
}