今天洛谷疯狂给我推送tarjan的题(它好像发现了我最近学tarjan),我正好做一做试一试(顺便练一练快读和宏定义)。
其实找割点的tarjan和算强连通分量的tarjan不一样,找割点的判定条件比较狗。
首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。
对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。
对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。
但这里也出现一个问题:怎么计算low[u]。
假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。
有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);
如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。
洛谷模板代码:
#include<iostream> #include<cstdio> #include<cstring> #define duke(i,a,n) for(int i = a;i <= n;i++) #define lv(i,a,n) for(int i = a;i >= n;i--) using namespace std; int dfn[200010],low[200010],lst[200010],len = 0; int ans = 0,top = 0,n,m,tot = 0,cut[200010]; bool vis[200010]; template <class T> void read(T &x) { char c; bool op = 0; while(c = getchar(),c < '0' || c > '9') if(c == '-') op = 1; x = c - '0'; while(c = getchar(),c >= '0' && c <= '9') { x = x * 10 + c - '0'; } if(op == 1) x = -x; } struct node{ int l,r,nxt; }a[200010]; void add(int x,int y) { a[++len].l = x; a[len].r = y; a[len].nxt = lst[x]; lst[x] = len; } void tarjan(int x,int fa) { int child = 0; dfn[x] = low[x] = ++tot; // stc[++top] = x; // vis[x] = 1; for(int k = lst[x];k;k = a[k].nxt) { int y = a[k].r; if(!dfn[y]) { tarjan(y,fa); low[x] = min(low[x],low[y]); if (low[y] >= dfn[x] && fa != x) cut[x]=true; if(x == fa) child++; } // else if(vis[y]) { low[x] = min(low[x],dfn[y]); } } if(x == fa && child >= 2) cut[x] = true; } int main() { memset(cut,false,sizeof(cut)); read(n); read(m); duke(i,1,m) { int x,y; read(x);read(y); add(x,y); add(y,x); } duke(i,1,n) { if(dfn[i] == 0) { tarjan(i,i); } } int num = 0; duke(i,1,n) { if(cut[i]) num++; } printf("%d ",num); duke(i,1,n) { if(cut[i]) printf("%d ",i); } return 0; } /* 6 7 1 2 1 3 1 4 2 5 3 5 4 5 5 6 */