Tarjan强连通分量
首先这个算法的作用就是缩点,将每一个强连通分量缩成一个点,从而保证缩点之后的图是一个DAG(有向无环图),然后我们再在这张图上进行各种各样的操作。
强连通分量
强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。
强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。
即强连通分量内的任意两点可相互抵达
代码实现
详细注解代码
//tarjan
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=1e4+10;
const int M=2e5+10;
int n,m;
int head[N],ver[M],Next[M],tot=-1;
//原始建图
int head2[N],ver2[M],edge2[M],Next2[M];
//缩点后建图
int w[N]; //每个点的点权
int rdu[N]; //这里是用来确定起点的
int cdu[N]; //这里是用来确定终点的
//spfa
int dist[N],q[N*2],l=0,r=0;
//tarjan变量集合
int idc=0,top=0; //时间戳 栈顶
int dfn[N]; //记录图的dfs序
int low[N]; //记录x所能到达的dfs序最小的子节点
int stack[N]; //栈 维护已经被遍历到 但是没有被确定属于哪一个强连通分量的点
int res[N]; //缩点之后的点权 (视题目而定)
int scc[N]; //记录缩点后x属于哪一个节点 即点x的强连通分量
int scnt=0; //缩点后节点(强连通分量)个数
//
bool vis[N];
//两次建图
void add(int x,int y)
{
++tot;
ver[tot]=y;
Next[tot]=head[x];
head[x]=tot;
}
void ADD(int x,int y,int z)
{
++tot;
ver2[tot]=y;
edge2[tot]=z;
Next2[tot]=head2[x];
head2[x]=tot;
}
void init()
{
memset(dist,0x3f3f3f3f,sizeof(dist));
memset(head2,-1,sizeof(head2));
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(rdu,0,sizeof(rdu));
memset(cdu,0,sizeof(cdu));
}
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+(ch-'0');
ch=getchar();
}
return x*f;
}
void tarjan(int x)
{
vis[x]=1; //dfs标记
++idc;
dfn[x]=idc;
//记录dfs序
low[x]=idc;
//第一次访问到该节点时 该节点所能到达的dfs序最小的点就是它自己
stack[++top]=x; //入栈
//遍历邻边
for(int i=head[x];i!=-1;i=Next[i])
{
int y=ver[i];
if(dfn[y]==0) //这个点没访问过 (相当于vis[y]==0)
{
tarjan(y); //访问
low[x]=min(low[x],low[y]);
//回溯时更新x所能到达的dfs序最小的点
//因为y是x的子节点 所以y可以到达的点 x也一定可以到达
}
else if(vis[y]==1)
//该点已被访问过 即存在x->y的一条后向边 (即从x指向某个dfs序比x小的点 这说明存在环)
//同样需要更新x所能到达的dfs序最小的点
{
low[x]=min(low[x],low[y]);
}
}
//dfs跑完 回溯
//此时对于栈内的点x来说 它上面的点都是它的子节点
//如果x所能到达的dfs序最小的点就是它自己
//那它和自己的 子节点(栈内x及它上面的点) 构成一个环(强连通分量)
//否则说明这个点在强连通分量中 但不是这个强连通分量中dfs序最小的点
if(low[x]==dfn[x]) //一个强连通分量
{
++scnt; //强连通分量++
//x和它前面的点构成一个强连通分量 将它们出栈 并记录
int sum=0; //记录该强连通分量的点权(题目需要)
do //遍历x前面所有的的点 它们属于同一个强连通分量
{
int y=stack[top];
vis[y]=0; //回溯 (一定记得写)
scc[y]=scnt; //它们属于同一个强连通分量
sum+=w[y];//记录该强连通分量的点权
}while(stack[top--]!=x);
res[scnt]=sum;//记录该强连通分量的点权
}
}
void spfa(int u) //spfa
{
dist[u]=0;
q[r++]=u;
while(l<r)
{
int x=q[l++];
for(int i=head2[x];i!=-1;i=Next2[i])
{
int y=ver2[i];
int z=edge2[i];
if(dist[y]>dist[x]+z)
{
dist[y]=dist[x]+z;
q[r++]=y;
}
}
}
}
int main()
{
init(); //初始化
//输入部分
n=read();
m=read();
for(int i=1;i<=n;i++)
{
w[i]=read();
}
for(int i=1;i<=m;i++)
{
int x=read();
int y=read();
add(x,y);
}
//输入部分
//缩点
for(int i=1;i<=n;i++)
{
if(dfn[i]==0)
{
//这个图可能是森林 对每一个连通块都跑一遍缩点
tarjan(i);
}
}
//缩点
//重新建图
//枚举每个点的边 按照强连通分量建图(这样会有许多重边 但链式前向星无视重边)
for(int u=1;u<=n;u++)
{
for(int i=head[u];i!=-1;i=Next[i])
{
int y=ver[i];
if(scc[u]!=scc[y]) //不在同一个强连通分量里 可以连边
{
ADD(scc[u],scc[y],-res[scc[y]]);
//点权转边权 用的肯定是y的点权了 (spfa跑最长路 建负边权)
rdu[scc[y]]++; //记录一下入度
cdu[scc[u]]++; //记录一下出度
}
}
}
//这样一张缩点之后的DAG图就建好了
return 0;
}