有向图强连通份量:在有向图G中,若是两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。若是有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通份量。ios
经过肉眼能够很直观地看出1-2-3是一组强连通份量,但很遗憾,机器并无眼睛,因此该怎么判断强连通份量呢?算法
上面那张图,咱们对它进行dfs遍历。能够注意到红边很是特别,由于若是按照遍历时间来分类的话,其余边都指向在本身以后被遍历到的点,而红边指向的则是比本身先被遍历到的点。数组
从一个点出发,一直向下遍历,而后忽得找到一个点,那个点居然有条指回这一个点的边!post
那么想必这个点可以从自身出发再回到自身spa
想必这个点和其余向下遍历的该路径上的全部点构成了一个环,code
想必这个环上的全部点都是强联通的。blog
可是咱们想要知道的是这个图的强联通份量。get
只须要知道这个点u下面的全部子节点有没有连着u的祖先就好了。string
但是咱们怎么知道这个点u它下面的全部子节点必定是都与他强联通的呢?it
这彷佛是不对的,这个点u之下的全部点不必定都强联通
那么怎么在退回到这个点的时候,知道全部和这个点u构成强连通份量的点呢?
开个栈记录就好了
具体实现还要看代码。
咱们须要几个数组来完成Tarjan算法
接下来咱们开始遍历每个点。
遍历到u时,首先初始化u的dfn和low都为当前节点的遍历序号。
而后将u打入栈中,去遍历它的每一个子节点,若是它的子节点没有被访问到,就访问,若是访问到了,就不去访问。u全部子节点的最小low值,即为u的low值
在u及其子节点访问完后,它的low值不再会变化。
若是作完了上面这一切,咱们如何判断u及其一些子节点可否变成一个强联通份量。
注意:这里,若是u的子节点已经本身成为了一个强联通份量,它已经出栈
试想一下,若是这时候u的dfn和low值相同,会怎么样?
两种状况:
若是是第1种状况,那么比u后遍历到的节点,在栈中的,必定能够和u构成一个强连通份量。
若是是第二种状况,表明着u的全部子节点都没法和u构成强连通份量,也就不在栈中。
因此,若是出现这种状况(u的dfn和low值相同),那么在栈中的u上面的节点(不是都说栈顶吗,咱们假设这个栈是站立的)和u必定能构成强连通份量。
不然,u的low值必定比dfn值小,这时必定会存在一个u的祖先节点,或是另外一分支的节点。
例题:https://www.luogu.com.cn/problem/P3387
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ld long double #define ull unsigned long long #define N 20000 #define M 100100 using namespace std; inline int Max(int a,int b){ return a>b?a:b; } inline int Min(int a,int b){ return a>b?b:a; } struct edge{ int to,next; inline void intt(int to_,int next_){ to=to_;next=next_; } }; edge li[M],li2[M]; int head[N],tail,head2[N],tail2; inline void add(int from,int to){ li[++tail].intt(to,head[from]); head[from]=tail; } inline void add2(int from,int to){ li2[++tail2].intt(to,head2[from]); head2[from]=tail2; } int dfn[N],low[N],stack[N],top,deep,co_num,co[N]; bool vis[N]; inline void tarjan(int u){ dfn[u]=++deep;low[u]=deep;stack[++top]=u;vis[u]=1; for(int x=head[u];x;x=li[x].next){ int to=li[x].to; if(!dfn[to]){ tarjan(to); low[u]=Min(low[u],low[to]); } else if(vis[to]) low[u]=Min(low[u],low[to]); } if(low[u]==dfn[u]){ co[u]=++co_num; vis[u]=0; while(stack[top]!=u){ co[stack[top]]=co_num; vis[stack[top]]=0; top--; } top--; } } int n,m,a[N],p[N],f[N]; inline void dfs1(int u){ vis[u]=1; for(int x=head[u];x;x=li[x].next){ int to=li[x].to; if(co[to]!=co[u]) add2(co[u],co[to]); if(!vis[to]) dfs1(to); } } inline int dfs2(int u){ if(f[u]) return f[u]; for(int x=head2[u];x;x=li2[x].next){ int to=li2[x].to; f[u]=Max(f[u],dfs2(to)); } f[u]+=p[u]; return f[u]; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=m;i++){ int from,to; scanf("%d%d",&from,&to); add(from,to); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i); for(int i=1;i<=n;i++) p[co[i]]+=a[i]; memset(vis,0,sizeof(vis)); for(int i=1;i<=co_num;i++) if(!f[i]) f[i]=dfs2(i); int maxx=-1; for(int i=1;i<=co_num;i++) maxx=Max(maxx,f[i]); printf("%d",maxx); return 0; }
这个题博主作麻烦了。实际上能够在原来的路径上直接改图,可是出题人没有像z*c同样爱卡空间和时间。因此能过。
tarjan算法仍是能够求割点的。
在一个无向图中,若是有一个顶点集合,删除这个顶点集合以及这个集合中全部顶点相关联的边之后,图的连通份量增多,就称这个点集为割点集合。
简单点说就是你把割点去掉后,这个图就好似被撕成了几个部分同样,各个部分之间再也不联通。
仍是同样,咱们继续用上面的dfn和low数组,可是这里,low数组的定义会有变化
定义以下:最先能绕到的点
由于是无向图,因此不存在祖先。
同时,若是比u晚遍历到的点的low值还比u的dfn小的话(或者等于),那么去掉点u,确定会多一个联通图。
同时还要注意判断dfs树的根节点,若是根节点儿子数目大于等于2,则根节点也是一个割点。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<sstream> #include<queue> #include<map> #include<vector> #include<set> #include<deque> #include<cstdlib> #include<ctime> #define dd double #define ll long long #define ld long double #define ull unsigned long long #define N 30010 #define M 200100 using namespace std; struct edge{ int to,next; inline void intt(int to_,int next_){ to=to_;next=next_; } }; edge li[M]; int head[N],tail; inline int Min(int a,int b){ return a>b?b:a; } inline void add(int from,int to){ li[++tail].intt(to,head[from]); head[from]=tail; } int dfn[N],low[N],deep; bool iscut[N]; inline void tarjan(int u,int fa){ dfn[u]=++deep;low[u]=deep; int child=0; for(int x=head[u];x;x=li[x].next){ int to=li[x].to; if(!dfn[to]){ child++; tarjan(to,u); low[u]=Min(low[u],low[to]); if(low[to]>=dfn[u]) iscut[u]=1; } else low[u]=Min(low[u],dfn[to]);//尤为注意这句话 } if(fa==-1&&child==1) iscut[u]=0; } int n,m; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int from,to; scanf("%d%d",&from,&to); add(from,to); add(to,from); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1); int ans=0; for(int i=1;i<=n;i++) if(iscut[i]) ans++; printf("%d\n",ans); for(int i=1;i<=n;i++) if(iscut[i]) printf("%d ",i); }