P2863 [USACO06JAN]The Cow Prom S(Tarjan)
思路:模板题。
==注意==:不能根据的来判断连通块的大小。
举个例子个结点,一条边即
显然遍历到2时,此时栈中有两个数,但是.会进入到涂色。但是只会涂一个点,因为它是孤立点。事实上可以有另外一种判断方法,因为某一个结点要么属于连通块,要么是一个孤立点,所以在判断时可以标记一下此时的是否大于1,然后看是否变为0了,如果变为0了说明属于一个大于1的连通块,否则是一个孤立结点,这种方法就不需要用到数组。
AC代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e4+5; #define mst(a) memset(a,0,sizeof a) int n,m,dfn[N],low[N],id,vis[N],ans,col[N],num[N],sum=0; vector<int>e[N]; //dfs[i]记录结点i遍历顺序,low[i]记录结点i及其子结点最小遍历顺序,vis[i]标记是否在栈中。 stack<int>s; //col[i]记录结点i属于那个连通块(本题没用),num[i]记录第i个连通块的结点数. void dfs(int u){ dfn[u]=low[u]=++id;//记录dfs顺序 s.push(u);//入栈 vis[u]=1;//标记入栈. for(auto v:e[u]){ if(!dfn[v]){ dfs(v); //没有遍历的点进行遍历然后更新low low[u]=min(low[u],low[v]); } else if(vis[v]) low[u]=min(low[u],low[v]);//如果已经遍历过而且在栈中 则取low较小值(这里是潜在的连通块) } if(dfn[u]==low[u]){ //该点是连通块的"根" 注意孤立点也是连通块. vis[u]=0; //回溯标记,并染色. col[u]=++sum; num[sum]++; while(s.top()!=u){ //依次出栈. col[s.top()]=sum; vis[s.top()]=0; s.pop(); num[sum]++; } s.pop(); } } int main(){ scanf("%d%d",&n,&m); for(int i=1,u,v;i<=m;i++){ scanf("%d%d",&u,&v); e[u].push_back(v); } for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); //可能存在孤立点,所以要dfs所有可能点. for(int i=1;i<=sum;i++) if(num[i]>1) ans++; printf("%d\n",ans); return 0; }
另一种判断方法:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e4+5; #define mst(a) memset(a,0,sizeof a) int n,m,s[N],dfn[N],low[N],id,top,vis[N],ans; vector<int>e[N]; void dfs(int u){ dfn[u]=low[u]=++id; s[++top]=u; vis[u]=1; for(auto v:e[u]){ if(!dfn[v]){ dfs(v); low[u]=min(low[u],low[v]); } else if(vis[u]) low[u]=min(low[u],low[v]); } if(dfn[u]==low[u]){ vis[u]=0; int f=0; if(top>1) f=1; while(s[top]!=u){ vis[s[top--]]=0; } top--; if(!top&&f) ans++; } } int main(){ scanf("%d%d",&n,&m); for(int i=1,u,v;i<=m;i++){ scanf("%d%d",&u,&v); e[u].push_back(v); } for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); printf("%d\n",ans); return 0; }