tarjan算法
定义
tarjan算法是一种求强连图分量的算法。
步骤
准备两个数组dfn[n]与low[n]
- dfn[i]:i 点访问到时候的时间戳 (可以通过是否为0判断该点有没有被访问过)
- low[i]:i 点所能访问到的最早的时间戳(初始化时和dfn[i]相同)
- 栈s:用于dfs时存放点
- in_stack[i] :判断i点是否在栈中
步骤:
- 外层循环遍历所有点,如果该点dfn[i]==0(没有被访问过),则dfs(u)
dfs(u)
- u点入栈。
- 遍历所有u能到达的边,对于u->v的边,有如下三种情况。见下图。
- 如果v没有被访问过则dfs(v),然后使得low[u] = min(low[u] , low[v])(因为,u能到达v,所有v能到达的最早时间戳,u也能到达)。(比如下图的边
2->3
) - 如果v已经被访问过,则判断v是否在栈中,如果v在栈中,则low[u] = min(low[u],low[v])此时是一条回路。(比如下图的边
4->1
) - 如果v已经被访问过,并且v不在栈中,则说明u->v是一条横叉边。什么都不做。 (比如下图的边
4->3
)
- 在判断完u的所有边后,如果dfn[u] == low[u],说明u不能到达更早的时间戳,则弹栈一直到把u弹出,这一轮弹出的点全都属于一个强连通分量。
例题
hdoj1269迷宫城堡
该题为判断所给的有向图是否为一个强连通图。因为此题不卡常,因此可以判断一共有几个强连通子图,如果不是1个则返回"No",否则返回"Yes"。
另外,这题数据很弱,之前写了一个板子中间有一步判断是否访问过(dfn[u]==0)时写成了v也AC了。
#include<cstdio>
#include<stack>
#include<algorithm>
using namespace std;
//此题不卡常,直接计算一共有几个强连通分量,然后判断是否等于1即可
const int maxn = 10004;
const int maxm = 100005;
struct{
int t;
int next;
}edge[maxm];
int cnt = 1;
int head[maxn];
void addedge(int u,int v){
edge[cnt].t = v;
edge[cnt].next = head[u];
head[u] = cnt++;
return;
}
stack<int> s;
bool in_stack[maxn];
int dfn[maxn]; //每个点访问到时的时间
int low[maxn]; //每个点所能到达的最早的时间戳
//值为0说明还没有被访问过
int n,m;
int time = 1;
int res = 0;
void init(){
cnt = 1;
for(int i=1;i<=n;i++)
dfn[i]=low[i]=head[i]=0;
//初始化时间戳与链式前向星
time = 1;//初始化起始时间
res = 0;
return;
}
void dfs(int u){ //当前访问u点
s.push(u);
dfn[u]=low[u] = time++;
in_stack[u] = true;
for(int i=head[u];i!=0;i=edge[i].next){
int v = edge[i].t;
if(dfn[v]==0){ //还没有被访问过了
dfs(v);
low[u] = min(low[u],low[v]);//v能到达u之前的时间戳(有回路)
}else if(in_stack[v]){ //换句话说,v也能到达u
low[u] = min(low[u],low[v]);
}//剩余的情况是横叉点,忽略
}
if(dfn[u]==low[u]){ //没能够回溯到更早的时间点
while(s.top()!=u){
in_stack[s.top()] = false;
s.pop();
}
in_stack[u] = false;
s.pop();
res++;
}
return;
}
int main(){
while(1){
scanf("%d%d",&n,&m);
if(n==0&&m==0) break;
init();
int u,v;
for(int i =0;i<m;i++){
scanf("%d%d",&u,&v);
addedge(u,v);
}
for(int i=1;i<=n;i++)
if(dfn[i]==0)//还没被访问过
dfs(i);
if(res==1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
应用(缩点)
通过生成强连通子图,可以把对应的强连通分量缩成一个点,从而转换成一个DAG图。
拓扑DP求DAG对最长路
DAG图
DAG图是有向无环图。因此可以通过求出拓扑顺序。就不过多解释,给出一道例题。
P1137 旅行计划,此题可以通过拓扑顺序,依次判断该点u有连边v(u->v)的的最长距离。也可以反向建图记忆化dfs搜索求解。
例题:缩点+DAG
P3387 【模板】缩点
最后这题结合了上述提到的两种算法,可以拿来练习。
AC代码
#include<cstdio>
#include<stack>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 10004;
const int maxm = 100005;
struct{
int t;
int next;
}edge[maxm],ed[maxm];
int cnt = 1;
int head[maxn],h[maxn]; //h,ed为缩点的链式前向星
void addedge(int u,int v){
edge[cnt].t = v;
edge[cnt].next = head[u];
head[u] = cnt++;
return;
}
int ff[maxn]; //所在强连通分量的父亲
int w[maxn]; //每个点的权值
stack<int> s;
bool in_stack[maxn];
int dfn[maxn]; //每个点访问到时的时间
int low[maxn]; //每个点所能到达的最早的时间戳
int time = 1;
int n,m;
int in[maxn]; //每个点的入度
queue<int> que; //用来拓扑排序
int dp[maxn]; //每个点为终点的最大路径
void tarjan(int u){ //当前访问u点
s.push(u);
dfn[u] = low[u] = time++;
in_stack[u] = true;
for(int i=head[u];i!=0;i=edge[i].next){
int v = edge[i].t;
if(dfn[v]==0){ //还没有被访问过了
tarjan(v);
low[u] = min(low[u],low[v]);//v能到达u之前的时间戳(有回路)
}else if(in_stack[v]){ //换句话说,v也能到达u
low[u] = min(low[u],low[v]);
}//剩余的情况是横叉点,忽略
}
if(dfn[u]==low[u]){ //没能够回溯到更早的时间点
while (1){
int y = s.top();
s.pop();
ff[y]=u;
in_stack[y]=false;
if (u==y) break;
w[u]+=w[y];
}
}
return;
}
void suotu(){ //建立缩图
int cnt2 = 1;
for(int u=1;u<=n;u++){
for(int i=head[u]; i!=0; i=edge[i].next){ //遍历老图
int x = ff[u],y = ff[edge[i].t];
if(x!=y){ //u和v不在一个强连通分量内
ed[cnt2].t = y;
ed[cnt2].next = h[x];
h[x] = cnt2++;
in[y]++; //入度
}
}
}
return;
}
int topo(){
for(int i=1;i<=n;i++)
if(ff[i]==i&&in[i]==0){
que.push(i);
dp[i] = w[i];
}
while(!que.empty()){
int u = que.front();
que.pop();
for(int i=h[u]; i!=0; i=ed[i].next){
int v = ed[i].t;
dp[v] = max(dp[v],dp[u]+w[v]);
in[v]--;
if(in[v]==0) que.push(v);
}
}
int res = 0;
for(int i=1;i<=n;i++)
res = max(res,dp[i]);
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i =0;i<m;i++){
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
}
for(int i=1;i<=n;i++)
if(dfn[i]==0)//该点还没被访问过
tarjan(i);//得到强连通分量
suotu();//得到缩图
printf("%d",topo());//拓扑求解
return 0;
}