强连通: 在一个有向图G里,设有两个点 a,b a , b ,由a有一条路可以走到b,由b又有一条路可以走到a,我们就叫这两个顶点(a,b)强连通。
强连通图: 如果 在一个有向图G中,每两个点都强连通,我们就叫这个图,强连通图。
强连通分量:在一个有向图G中,有一个子图,这个子图每2个点都满足强连通,我们就叫这个子图叫做 强连通分量 [分量::把一个向量分解成几个方向的向量的和,那些方向上的向量就叫做该向量(未分解前的向量)的分量]
原题地址:https://www.luogu.org/problemnew/show/P3387
思路:tarjan缩点之后,重新建图,使用spfa求解最长路。(就是最短路的变形)
具体见代码
/*
有向图求缩点
*/
#include
using namespace std;
const int maxn = 1e4 + 5;
int n, m;
struct node {
int nxt, v, u;
} e[maxn * 10];
int head[maxn], Index, dfn[maxn], low[maxn], scc, top;
int belog[maxn], tot, inStack[maxn], Stack[maxn], in[maxn];
//int num[maxn];//各个强连通分 量包含点的个数
int value[maxn], vis[maxn], dis[maxn];
vector<int>G[maxn];
int ret[maxn];
void init() {
memset(vis, 0, sizeof(vis));//spfa中判断是否已经在队列中
memset(dis, 0, sizeof(dis));//用于求距离
memset(value, 0, sizeof(value));//保存点权
memset(head, -1, sizeof(head));//用于前向星
memset(belog, 0, sizeof(belog));//判断原来的顶点i属于缩点后的的哪个顶点
memset(dfn, 0, sizeof(dfn));//时间戳
memset(low, 0, sizeof(low));//判断顶点所能返回的最早的节点
memset(in, 0, sizeof(in));//判断入度
memset(inStack, 0, sizeof(inStack));//判断是否在栈中
// memset(num, 0, sizeof(num));
memset(ret, 0, sizeof(ret));//缩点后点权的总值
Index = 0;//时间戳
tot = 0;//前向星
top = 0;//模拟栈
scc = 0;//联通分量个数
}
void add_edge(int u, int v) {//加边操作
e[tot].u = u;
e[tot].v = v;
e[tot].nxt = head[u];
head[u] = tot++;
}
void tarjan(int u) {
low[u] = dfn[u] = ++Index;//初始化
inStack[u] = 1;//表明u节点已经在栈中
Stack[top++] = u;//将u入栈
for(int i = head[u]; ~i; i = e[i].nxt) {
int v = e[i].v;
if(!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if(inStack[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u]) {//如果想等,就说明找到了一个强连通分量
scc++;
int v;
do {
v = Stack[--top];
inStack[v] = 0;
belog[v] = scc;
// num[scc]++;
ret[scc] += value[v];
} while(v != u);
}
}
void solve() {
for(int i = 1; i <= n; i++ ) {
//为了求出所有的连通分量,如果保证是连通图,那么可以不用循环
if(!dfn[i])tarjan(i);
}
}
int spfa(int num) {
queue<int>q;
q.push(num);
dis[num] = ret[num];
while(!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for(int i = 0; i < G[u].size(); i++ ) {
int v = G[u][i];
dis[v] = max(dis[v], dis[u] + ret[v]);
if(!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
int ans = 0;
for(int i = 1; i <= scc; i++) ans = max(ans, dis[i]);
return ans;
}
int main() {
init();
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &value[i]);
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
add_edge(u, v);
}
solve();
for(int i = 0; i < tot; i++) {//重新构图
int t1 = belog[e[i].u];
int t2 = belog[e[i].v];
if(t1 == t2) continue;
G[t1].push_back(t2);
in[t2]++;
}
int ans = 0;
for(int i = 1; i <= scc; i++) {
if(!in[i]) {//如果是最大值,那么是以入度为0的节点开始的
//因为这一定是最大的 ,贪心
memset(vis, 0, sizeof(vis));
ans = max(ans, spfa(i));
}
}
printf("%d\n", ans);
return 0;
}
//5 6
//3 1
//1 2
//2 3
//4 3
//4 5
//5 3