算法简介
强连通:有向图 G 中任意两个结点连通。
强连通分量(SCC):极大的强连通子图。
Tarjan算法的原理是利用DFS生成树求强连通分量。
其中为图中每个节点
u
u
u维护了以下变量:
变量 | 解释 |
---|---|
dfn[u] | 节点 u u u在深度优先搜索中的次序,简称dfs序 |
low[u] | 以节点 u u u为根的子树中,通过一条不在搜索树上的边到达的节点dfn的最小值 |
实例
求割点
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int M =2e5+7;
const int N =2e4+7;
int e[M],ne[M],idx,h[N];
int dfn[N],low[N],now,root;
int tot;
bool cut[N];
void add(int u,int v)
{
e[++idx]=v;
ne[idx]=h[u];
h[u]=idx;
}
void dfs(int u)
{
dfn[u]=low[u]=++now;
int flag=0;
for(int i=h[u];i;i=ne[i])
{
int v=e[i];
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
//割点判定法则
if(dfn[u]<=low[v]&&(++flag>=2||u!=root)&&!cut[u])
cut[u]=1,tot++;
}
else low[u]=min(low[u],dfn[v]);
}
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1,u,v;i<=m;i++)
scanf("%d%d",&u,&v),add(u,v),add(v,u);
for(int i=1;i<=n;i++)
if(!dfn[i])root=i,dfs(i);
printf("%d\n",tot);
for(int i=1;i<=n;i++)
if(cut[i])printf("%d ",i);
return 0;
}
求强连通分量并缩点
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e4 + 7;
const int M = 1e5 + 7;
struct edge{int from,to,ne;}edges[M];
int he[N],eidx;
void add_edge(int u,int v)
{
edges[++eidx].from=u;
edges[eidx].to=v;
edges[eidx].ne=he[u];
he[u]=eidx;
}
int h[N],ne[M],e[M],w[N],ru[N],idx;
void add(int u,int v)
{
e[++idx]=v;
ne[idx]=h[u];
h[u]=idx;
ru[v]++;
}
int dfn[N],low[N],stk[N],sidx,times;
int id[N];
bool vis[N];
void dfs(int u)
{
dfn[u]=low[u]=++times;
stk[++sidx]=u,vis[u]=1;
for(int i=he[u];i;i=edges[i].ne)
{
int v=edges[i].to;
if(!dfn[v])
{
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])low[u]=min(low[u],dfn[v]);
}
//一个强连通分量的根节点
if(dfn[u]==low[u])
{
while(sidx)
{
int top=stk[sidx--];
vis[top]=0;
id[top]=u;
if(top==u)break;
//强连通分量中所有的边权缩为一个点
w[u]+=w[top];
}
}
}
int dist[N];
int main()
{
int n,m,u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
for(int i=1;i<=m;i++)scanf("%d%d",&u,&v),add_edge(u,v);
for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);
//重新建图进行缩点
for(int i=1;i<=m;i++)
{
int u=id[edges[i].from],v=id[edges[i].to];
if(u!=v)add(u,v);
}
//本题实际上不需要拓补排序求dp顺序,因为缩点后的图是一个DAG
queue<int> q;
for(int i=1;i<=n;i++)
if(!ru[i])q.push(i),dist[i]=w[i];
while(q.size())
{
int top=q.front();q.pop();
for(int i=h[top];i;i=ne[i])
{
int v=e[i];
dist[v]=max(dist[v],dist[top]+w[v]);
if(--ru[v]==0)q.push(v);
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,dist[i]);
cout<<ans<<endl;
return 0;
}