摘要:
应用Tarjan算法求解强联通分支
问题简述:
给定n个点,m条边的有向图。每一个点都有一个权重 w [ i ] w[i] w[i].请求出一条路径,使得路径经过的点的权重值和最大
原题链接:洛谷P2863缩点
算法分析:
在Tarjan算法求割点的基础上,使用栈(或者队列)保存每一个连通分支包含的点的编号。特别的一个联通分支以该连通分支最先遍历到的点为代表。
代码以及详细注释:
#include <iostream>
#include <stdio.h>
#include <vector>
#include <queue>
#pragma warning(disable:4996)
#define INF -2147483647
using namespace std;
struct edge {
int to;
int from; //根据题目要求自行添加,其实不要也行
int next;
};
class Solution {
public:
int n, m;
vector<int> visit; //标记某点是否到达过
vector<int> dfn; //某点第一次访问的时间戳
vector<int> low; //以改点为根的最小的子树的编号
vector<int> sta; //用于保存遍历的点
vector<int> sd; //缩点后的结果,sd[i]表示点i所在的连通分量的编号(根节点的编号)
int index = 0; //栈顶的位置
int tp = 0; //时间戳
int ans = INF;
vector<int> w; //用于保存每一个点的权重大小
vector<edge> e; //缩点之前原图的边集
vector<int>head;
int cnt = 0;
vector<edge> en; //缩点之后的图的边集
vector<int> headn;
int cntn=0;
inline void add_edge(int u,int v)
{
//为原图添加边
cnt++;
e[cnt].to = v;
e[cnt].from = u;
e[cnt].next = head[u];;
head[u] = cnt;
}
void function() {
cin >> n >> m;
//初始化
visit.resize(n + 1,false);
dfn.resize(n + 1,0);
low.resize(n + 1,0);
sta.resize(n + 1);
sd.resize(n + 1, 0);
e.resize(m + 1);
en.resize(m + 1);
head.resize(n + 1, 0);
headn.resize(n + 1, 0);
w.resize(n + 1, 0);
//记录输入的权重
for (int i = 1; i <= n; ++i)
cin >> w[i];
for (int i = 1; i <= m; ++i)
{
int x, y;
cin >> x >> y;
add_edge(x, y);
}
for (int i = 1; i <= n; ++i) {
if (!dfn[i])
tarjin(i);
}
//缩点之后开始建立新图
for (int i = 1; i <= m; ++i)
{
int u = sd[e[i].from];
int v = sd[e[i].to];
if (u != v)
{
++cntn;
en[cntn].from = u;
en[cntn].to = v;
en[cntn].next = headn[u];
headn[u] = cntn;
}
}
topsort();
cout << ans;
}
void tarjin(int u){
dfn[u] = low[u] = ++tp;
sta[++index] = u; //进栈
visit[u] = 1;
for (int i = head[u]; i != 0; i = e[i].next) {
int y = e[i].to;
if (!dfn[y])
{
//如果没有访问过
tarjin(y);
low[u] = min(low[u], low[y]);
}
else if (visit[y])
{
//如果访问过 并且还在栈中
low[u] = min(low[u], low[y]);
}
}
if (low[u] == dfn[u]) {
//说明点u就是该连通分量的根节点.开始缩点
int y;
while(y=sta[index--])
{
sd[y] = u;
visit[y] = 0;
if (u == y)
break;
w[u] += w[y];
}
}
}
void topsort() {
queue<int> q;
vector<int> dis;
vector<int> in;
in.resize(n + 1, 0);
dis.resize(n + 1, 0);
for (int i = 1; i <=m; ++i)
{
//遍历新图的每一条边,记录出度
in[en[i].to]++;
}
for (int i = 1; i <=n; ++i)
{
if (sd[i] == i && in[i] == 0)
{
//以每一个连通分支最先遍历到的点作为该连通分支的代表
q.push(i);
dis[i] = w[i];
}
}
//拓扑排序模板
while (!q.empty()) {
int temp = q.front();
q.pop();
for (int i = headn[temp]; i != 0; i = en[i].next) {
int to = en[i].to;
dis[to] = max(dis[to], dis[temp] + w[to]);
in[to] --;
if (in[to] == 0)
q.push(to);
}
}
for (int i = 1; i <= n; ++i)
ans = max(ans, dis[i]);
}
};
int main() {
//freopen("in.txt", "r", stdin);
Solution s;
s.function();
return 0;
}