本文章bug可能比较多,也可能讲的不是很完美,所以难免会经过多次修改,如有不恰当,还请各位大佬赐教
本文大量参考吴永辉教授在2020年度集训讲课内容,可以在网站上进行视频的查找
2021-3-24写了两行
代码里加了注释,但是还不是非常完美,仍要完善~
先存个模板,继续更
2021-3-25第一次更
Tarjan算法是罗伯特 · 塔扬提出的一种求解有向图的强连通分量的一种算法,除了求强连通分支,还可以用来求图的割点和桥,以及点(边)双连通分支,非常的强大,如果想要一睹Tarjan真容,可以移步 bilibili
如果两个点之间可以相互到达,那么说这两个点之间的关系就是强联通的,如果在一个图中,任意两点之间都是可以相互到达的,那么说这个图就是个强连通图。在一个图中,有向图的极大强连通子图叫做这个图的强连通分量SCC
比如说有这么一张图:
在这张图中,{1,3,2,4},{5},{6}三部分就是强联通的
Tarjan算法是基于DFS的算法,每个强连通分量为当前搜索树中的一棵子树。在算法进行时,将当前的节点1放到栈里面。这样一来,在回溯时,就可以判断栈顶到栈中的节点是否为一个强连通分量
敲黑板
在Tarjan算法推进的过程中,一些需要定义的数组以及变量,现在首先进行声明一下:
dfn[x],表示节点x的时间戳,通俗说就是当前这个点x被访问的次序,第一个被访问的dfn[x]是1,第二个dfn[x] = 2
low[x],表示节点x或者是x的子树能够追溯到1的最早的栈中节点的编号;low[x]一开始是dfn[x],然后会不断更新,成为强连通分支子树根节点的dfn,当dfn[x] == low[x]的时候,以x为根的搜索子树上所有的节点是一个强连通分支
stack st;实现栈在Tarjan中的作用;
pos[x] or colour[x]可以记录节点x所在的强连通分支是第几个
为了方便,直接手写这个1过程吧
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 7; int n,m; int head[maxn]; int tim,cnt; struct node { int v; int nex; } e[maxn]; void init() { cnt = 0; tim = 0; for(int i=1; i<=n; i++) { head[i] = -1; } } void add(int u,int v) { e[cnt].v = v; e[cnt].nex = head[u]; head[u] = cnt ++; } stack<int> st; //low[x] -> x or x子树能够到达的栈中节点的次序号 int dfn[maxn],low[maxn],pos[maxn]; int tot; /// pos[i] == x -> 点i在第x个强连通分支 void Tarjan(int x) { dfn[x] = low[x] = ++tim;///时间戳 st.push(x);///节点入栈 for(int i=head[x]; ~i; i=e[i].nex) { int v = e[i].v;///有向边 if(!dfn[v]) {//节点v没有被访问过,是树枝边 Tarjan(v);//首次被访问不在栈内 low[x] = min(low[x],low[v]); } else if(!pos[v]) {///被访问过,在栈内 //遇到已经在栈内的点,就将该点作为强连通分量的根 low[x] = min(low[x],dfn[v]); } }//low[x] 没有更新,在栈中节点u以及之上节点构成一个强连通分支 if(low[x] == dfn[x]) { pos[x] = ++tot;///记录强连通分支的个数 while(st.top() != x) { pos[st.top()] = tot;//栈中节点u以及之上节点在第tot个强连通分支中 st.pop(); } st.pop();//节点u出栈 } } int main() { cin >> n >> m; init(); for(int i=1; i<=m; i++) { int a,b; cin >> a >> b; add(a,b); } Tarjan(1);///wrong for(int i=1;i<=n;i++){///yes if(dfn[i] == 0) Tarjan(i); } cout<<tot<<endl; return 0; }