解释
缩点,就是把一张有向有环图中的环缩成一个个点,形成一个有向无环图
1.dfn和low的含义
tarjan算法有两个核心数组:dfn和low,同样也需要一个栈,辅助存储强连通分量。dfn是“时间戳”,dfn[i]说明i点在什么时间被遍历的。low[i]是i可回溯到的最早时间戳的点的时间戳(见下面对low值的计算),默认值为dfn[i],举个例子:如果i到j有一条路径,j到i也有一条路径,那么就使 i -> j -> i 的环中所以点中 low 变为最早时间戳 并入栈,打上在栈内的标记in。
对于一个有向图的DFS的搜索树(i 可以到 j,j 不一定能到 i),如下
里面的强连通分量有 { 6 , 7 , 8 } ,{ 4 } , { 3 } , { 2 } , { 1 } , { 9 }
而强连通分量产生的环 { 6 , 7 , 8 } 是有父子关系的,所以在 { 2 , 3 , 4 , 9 } 这个环不是强连通分量,因为搜索树是有向的(3 , 9 可以到 4 , 而 4 不能访问回去)
在用Tarjan算法时,栈中的点一定是有父子关系的
- dfn[ i ] 数组表示遍历到点 i 时DFS的次数
- low[ i ] 数组表示点 i 到栈中
在搜索的过程中会先搜索 1——2——3——4,然后在到 9 的时候就会有这种情况
在用Tarjan算法时,用栈存储的点有 { 1 , 2 , 3 , 9 } ,这时还未遍历点 4,点 4 不在栈中,与点 9 没有父子关系
遍历到点 4 ,此时点 4 无法往下遍历,且与栈中点 9 无父子关系,只能退出栈,有强连通分量 { 4 },然后回溯
依次退栈,有强连通分量 { 3 },{ 9 },{ 2 },{ 1 }
遍历到点 7,栈中的点有 { 5, 6 , 7 }
往下遍历到点 8,栈中点 { 5, 6,7,8 },到点 8 后往下遍历到点 6,点 6 为栈中点,则点 8 到栈中深度最小的点为点 6,low[ 8 ] = 6
low[8] < low[7],说明点 6 可以到点 8,点 8 也可以到点 6 ,这其中的点都可以相互到达
然后就将放入栈中的点 8,7 取出(按入栈顺序),最后取出点 6 自己时停止
则 { 6,7 , 8 } 为强连通分量
代码
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e4 + 5;
const int M = 1e5 + 5;
struct node{
int v, u;
int next;
}e[M], ed[M];
int p[N], head[N], in[N];
int dfn[N], low[N], sd[N], tim;
int stac[N], vis[N], top;
int n, m, cnt, ans[N];
void add1(int u, int v) {
e[++cnt].v = v;
e[cnt].u = u;
e[cnt].next = head[u];
head[u] = cnt;
}
void add2(int u, int v) {
ed[++cnt].v = v;
ed[cnt].u = u;
ed[cnt].next = head[u];
head[u] = cnt;
in[v]++;
}
void tarjan(int u) {
low[u] = dfn[u] = ++tim; //tim表示在栈中的编号
stac[++top] = u, vis[u] = 1;
for(int i = head[u]; i; i = e[i].next) { //搜索相连节点
int v = e[i].v;
if(!dfn[v]) { //没搜索过
tarjan(v);
low[u] = min(low[u], low[v]); //更新所能到的上层节点
} else if(vis[v]) low[u] = min(dfn[v], low[u]);//在队中 找到栈中最上端的节点
//dfn是栈中编号或时间戳,如果s在栈中,则x到栈中最上端节点为dfn[s]
}
if(dfn[u] == low[u]) { //找到了一个强联通分量,开始弹栈,直到弹到当前这一个点为止
int v;
while(v = stac[top--]) {
sd[v] = u; // 染色
vis[v] = 0;
if(u == v) break;
p[u] += p[v]; // 统计每一个颜色的点的总权值
}
}
}
int tuopu() { // 拓扑排序
queue<int> q;
for(int i = 1; i <= n; ++i) {
if(!in[i]) {
q.push(i);
ans[i] = p[i];
}
}
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i = head[u]; i; i=ed[i].next) {
int v = ed[i].v;
ans[v] = max(ans[v], ans[u]+p[v]); // dp求最大路
if(--in[v] == 0) q.push(v);
}
}
int sum = 0;
for(int i = 1; i <= n; ++i) // 在每个点的最大路中找到最大的
sum = max(sum, ans[i]);
return sum;
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; ++i)
cin >> p[i];
for(int i = 1; i <= m; ++i) {
int u, v;
cin >> u >> v;
add1(u, v);
}
for(int i = 1; i <= n; ++i) //找强连通分量
if(!dfn[i]) tarjan(i);
cnt = 0;
memset(head, 0, sizeof(head)); //初始化
for(int i = 1; i <= m; ++i) {
int u = sd[e[i].u], v = sd[e[i].v];// 建新图
if(u != v) add2(u, v);
}
int ans = tuopu();
printf("%d\n", ans);
return 0;
}