双连通分量。
题目大意:给出一个连通图,求至少添加多少条边,使得对于任意亮点,不只一条路。即两点间的路去掉一条边还是连通的。
思路:用双联通缩点,然后求出度为一的双连通分量的个数count1,最后(count1+1)/2即是所求!!
这个题基本上算是我接触双连通的第一道题,写了好多个版本,最后还是稍微了理解了一些双联通。。
双连通分量的tarjan用不用栈都可以,因为它不会存在横向边,不需要考虑下一个点是否在栈中。。
if(low[u]==dfn[u])
{
count++;
do{
v=S.top();
S.pop();
vis[v]=0;
Belong[v]=count;
}while(v!=u);
}
用栈的话可以很明显的看出有几个双连通分量(即count),然后某一个点属于某一个双连通分量也很清楚!
void tarjan(int u,int father)
{
int j,v;
dfn[u]=low[u]=cnt++;
visit[u]=1;
for(j=head[u];j!=-1;j=edge[j].next)
{
v=edge[j].to;
if(v==father) continue;
if(!visit[v]) tarjan(v,u);
low[u]=min(low[u],low[v]);
}
}
不用栈的话low[i]即是指i所在的双联通分量,比较简洁,不用开很多数组!
对这道题而言,需要去掉重边。
贴一个比较简洁一点的代码:
1 # include<stdio.h>
2 # include<string.h>
3 # define N 5005
4 struct node{
5 int from,to,next;
6 }edge[N*4];
7 //bool adj[N][N];
8 int tol,head[N],visit[N],dfn[N],low[N],n,m,cnt,degree[N];
9 void add(int a,int b)
10 {
11 edge[tol].from=a;edge[tol].to=b;edge[tol].next=head[a];head[a]=tol++;
12 }
13 int min(int a,int b)
14 {
15 return a<b?a:b;
16 }
17 void tarjan(int u,int father)
18 {
19 int j,v;
20 dfn[u]=low[u]=cnt++;
21 visit[u]=1;
22 for(j=head[u];j!=-1;j=edge[j].next)
23 {
24 v=edge[j].to;
25 if(v==father) continue;
26 if(!visit[v]) tarjan(v,u);
27 low[u]=min(low[u],low[v]);
28 }
29 }
30 int judge(int a,int b)//判断是否出现重边
31 {
32 int j;
33 for(j=head[a];j!=-1;j=edge[j].next)
34 {
35 if(b==edge[j].to) return 0;
36 }
37 return 1;
38 }
39 int main()
40 {
41 int i,count1,a,b;
42 while(scanf("%d%d",&n,&m)!=EOF)
43 {
44 tol=cnt=0;
45 memset(head,-1,sizeof(head));
46 //memset(adj,0,sizeof(adj));
47 for(i=1;i<=m;i++)
48 {
49 scanf("%d%d",&a,&b);
50 if(judge(a,b))
51 {
52 add(a,b);
53 add(b,a);
54 }
55 }
56 memset(visit,0,sizeof(visit));
57 tarjan(1,0);
58 memset(degree,0,sizeof(degree));
59 for(i=0;i<tol;i++)
60 {
61 a=edge[i].from;
62 b=edge[i].to;
63 if(low[a]!=low[b]) degree[low[b]]++;
64 }
65 count1=0;
66 for(i=0;i<cnt;i++)
67 if(degree[i]==1) count1++;
68 printf("%d\n",(count1+1)/2);
69 }
70 return 0;
71 }