题目传送门:HDU1269
今天,突发奇想,对于强连通分量产生了浓厚的兴趣,于是就想学一学。
当然,对于一个初学的蒟蒻来说,Tarjan算法是比较简单易懂的(因为我打的就是Tarjan),我选的题目也是比较水。
在Tarjan算法里,最难懂的就是一个节点所在子树的最早搜索次序了,这些我都会详细讲解。
搜索次序还是比较好懂的,如果会搜索的同学应该都懂什么是搜索次序吧。
现在我们假设我们已经搜到节点x,当前搜索树的大小为size,一个节点所在子树的最早搜索次序为t,搜索次序为d。
节点x的搜索次序为size+1(这一点应该没问题吧)。
我们遍历所有节点x可以到达的节点y:
1.若节点y没有被搜索过,则搜索节点y,t[x]=min(t[x],t[y])。
2.若节点y被搜索过且节点y所在的强连通分量未被确定,t[x]=min(t[x],d[y])。
若遍历完所有节点y后,t[x]=d[x],说明以u为根的搜索子树上所有节点是一个强连通分量。
内个……你问我怎么证明?我也不知道啊……但是画图之后,好像是举不出反例的吧。就把这个当成一个结论来用吧。
可以用一个栈来求每一个强连通分量中有哪些元素,具体的,看代码吧……
对于我的代码,可能需要一些注释:ans表示这张图中有几个强连通分量,size表示当前的搜索树的大小,sy数组表示该节点属于哪一个强连通分量。
b数组的作用:对于节点x,若b[x]=0,说明该节点未被搜索过或该节点所在的强连通分量已确定;若b[x]=1,说明该节点已被搜索过,且该节点所在的强连通分量未确定。
有人可能会问:你的b[x]=0有两个含义,搜索时不会重复搜索吗?
请这些同学好好阅读一下下面的代码,并画图辅助,你会发现:对于节点x,当该节点被搜索完毕时,d[x]!=0,b[x]=0。再次搜索到这个节点时,好像会被略过……
就这样吧……(为自己的代码的简洁点个赞)
附上AC代码:
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
using namespace std;
stack <int> s;
vector <int> map[10010],scc[10010];
int n,m,t[10010],d[10010],b[10010],size,ans,x,y,sy[10010];
void intt(void){
memset(t,0,sizeof t),memset(d,0,sizeof d),memset(b,0,sizeof b),size=ans=0;
for (int i=1; i<=n; ++i) map[i].clear(),scc[i].clear();
while (!s.empty()) s.pop();
}
void so(int x){
b[x]=1,t[x]=d[x]=++size,s.push(x);
for (int i=0; i<map[x].size(); ++i)
if (!d[map[x][i]]) so(map[x][i]),t[x]=min(t[x],t[map[x][i]]);
else if (b[x]) t[x]=min(t[x],d[map[x][i]]);
if (d[x]==t[x]){
++ans;
while (!s.empty()){
int p=s.top();s.pop(),b[p]=0,sy[p]=ans,scc[ans].push_back(p);
if (p==x) break;
}
}
return;
}
int main(void){
while (scanf("%d%d",&n,&m)&&n+m){
intt();
for (int i=1; i<=m; ++i) scanf("%d%d",&x,&y),map[x].push_back(y);
for (int i=1; i<=n; ++i) if (!d[i]) so(i);
if (ans>1) puts("No"); else puts("Yes");
}
return 0;
}