2021-03-01 洛谷P2863缩点——Tarjan算法应用(二)

摘要:

应用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;
}

猜你喜欢

转载自blog.csdn.net/sddxszl/article/details/114271718
今日推荐