题目链接:http://poj.org/problem?id=2186
一、多日不写图论,链式前向星都写错
for(i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
addEdge(u-1,v-1,i);/*此处如果addEdge(u,v,i),后面的addEdge(u-1,v-1,i);就会有问题,因为Tarjan(0)无意义*/
}
for(i=0;i<n;i++)
if(dfn[i]==-1)Tarjan(i);
先解释名词:dfn[v]:节点v的深度优先数,就是DFS遍历的深度,从1开始。
low[v]:v和v的子孙的最小值
这里有一点:想想low[u]的值什么时候会被改变?
DFS初始的时候low[u]被初始化为深度优先数,它被改变必然是子孙的low比它的low小,
在想想,既然子孙是通过DFS遍历,并且是后来被遍历的,那么深度优先数应该比low[u]大啊,怎么会小呢?
答案是:因为有回边。(这个看图论的书可以看明白什么叫回边,交叉边,很多资料避开了,但我觉得认真学习算法的话,这些还是学学吧)
low[u]的求法:
low[u]=Min{dfn[u],Min{low[w]|w是u的一个子女},Min{dfn[v]|v与u邻接,且(u,v)是一条回边}}
区分w是u的子女还是(w,u)是回边的方法:dfs的时候,若与当前u邻接的点w已经被访问过,则(w,u)是回边,否则w是u的子女
我觉得low的值应该这样理解:如low[u]=depth,意味着从low出发,可以到达的最低的深度为depth,这样想的话,Tarjan就可以有个模糊的认识了。某个点u,low[u]==dfn[u],从u出发,可以再回到u,假设从u到u的路径为u,v1,v2,v3,v4,v5,v6,u,那么从v2出发,可以通过v2,v3...v5的路径到v5,也可以从v5出发,通过v5,v6,u,v1,v2到v2,试试吧,这个从u到u的任意两个点i,j,都有从i到j的路和从j到i的路,这不就是强连通分量的定义?
有了这个逻辑,再去看算法模板应该很容易了。
三、多嘴一句,谈谈对判断回边生成树的边的做法:一种就是上面写的区分w是u的子女还是(w,u)是回边的方法:dfs的时候,若与当前u邻接的点w已经被访问过,则(w,u)是回边,否则w是u的子女,另一种就是这个代码模板的做法:其实异曲同工,
min1=min(min1,low[v]);
if(min1<low[u]){low[u]=min1;return;}如果发生了min1<low[u],说明有从u的子孙到u的祖先的回边,则绝不可能low[u]==low[v],所以此处return
#include<cstdio> #include<cstring> #include<algorithm> #include<stack> #include<cstdlib> using namespace std; #define M 50002 #define N 10002 stack<int>st; int head[N],id[N],low[N],dfn[N]; int scnt,cnt,n,m; //cnt记录访问的次序 struct node{ int to,next; int id; }edge[M]; void addEdge(int u,int v,int k) { edge[k].to=v; edge[k].next=head[u]; head[u]=k; } void Tarjan(int u) { int k,v,t; int min1=dfn[u]=low[u]=cnt++; st.push(u); for(k=head[u];k!=-1;k=edge[k].next) { v=edge[k].to; if(dfn[v]==-1)Tarjan(v); min1=min(min1,low[v]); } if(min1<low[u]){low[u]=min1;return;} do { id[t=st.top()]=scnt;//缩点操作一 low[t]=n;//这一句别忘了,否则这个节点会被多次遍历 st.pop(); }while(t!=u); scnt++; }
memset(head,-1,sizeof(head)); memset(dfn,-1,sizeof(dfn)); for(i=0;i<n;i++) if(dfn[i]==-1)Tarjan(i);
不多言了,贴代码
#include<cstdio> #include<cstring> #include<algorithm> #include<stack> #include<cstdlib> using namespace std; #define M 50002 #define N 10002 stack<int>st; int head[N],id[N],low[N],dfn[N],in[N],out[N]; int scnt,cnt,n,m,n2; //cnt记录访问的次序,n2记录 struct node{ int to,next; int id; }edge[M]; void addEdge(int u,int v,int k) { edge[k].to=v; edge[k].next=head[u]; head[u]=k; } void Tarjan(int u) { int k,v,t; int min1=dfn[u]=low[u]=cnt++; st.push(u); for(k=head[u];k!=-1;k=edge[k].next) { v=edge[k].to; if(dfn[v]==-1)Tarjan(v); min1=min(min1,low[v]); } if(min1<low[u]){low[u]=min1;return;}/*这里我写成if(min1<low[v]){low[u]=min1;return;} WA了无数次*/ do { id[t=st.top()]=scnt;//缩点操作一 low[t]=n; out[scnt]++; st.pop(); }while(t!=u); scnt++; } int main() { int i,j,u,v,ans,flag; while(scanf("%d%d",&n,&m)!=EOF) { memset(head,-1,sizeof(head)); memset(dfn,-1,sizeof(dfn)); memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); scnt=cnt=ans=0; for(i=0;i<m;i++) { scanf("%d%d",&u,&v); addEdge(u-1,v-1,i);/*此处如果addEdge(u,v,i),后面的addEdge(u-1,v-1,i);就会有问题,因为Tarjan(0)无意义*/ } for(i=0;i<n;i++) if(dfn[i]==-1)Tarjan(i); for(i=0;i<n;i++) for(j=head[i];j!=-1;j=edge[j].next) { int a=id[i],b=id[edge[j].to]; if(a!=b)in[id[i]]++; } for(i=0;i<scnt;i++) if(!in[i]) { ans++; flag=i; } if(ans==1)printf("%d\n",out[flag]); else printf("0\n"); } return 0; }