【模板】缩点(tarjan + 拓扑排序)

31 篇文章 0 订阅

题目链接

题目描述

给定一个 n 个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

输入格式

第一行两个正整数 n, m

第二行 n 个整数,依次代表点权

第三至 m + 2 行,每行两个整数 u, v,表示一条 u → v 的有向边。

输出格式

共一行,最大的点权之和。

输入输出样例

输入

2 2
1 1
1 2
2 1

输出

2

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n, m, cnt, s, sum, num;	// n 个点,m 条边,cnt 强连通分量 
int K[maxn], head[maxn], h[maxn], p[maxn], dis[maxn], in[maxn];	// 	K 表示每个节点所属的强联通分量
int DFN[maxn], low[maxn];	// DFN[i] 表示 i 点的进入时间,low[i]表示从 i 点出发,所能访问到的最早的进入时间
bool inS[maxn];
stack<int> S;

struct edge{
	int from, to, next;
}edge1[maxn * 10], edge2[maxn * 10];

void add(int x, int y)
{
	edge1[++sum].next = head[x];
	edge1[sum].from = x;
	edge1[sum].to = y;
	head[x] = sum;
}

void Tarjan(int x)
{
	num++;
	DFN[x] = low[x] = num;	// num 表示在栈中的编号
	inS[x] = 1;
	S.push(x);
	for(int i = head[x]; i; i = edge1[i].next){		// 搜索相连节点 
		int s = edge1[i].to;
		if(!DFN[s]){	// 没搜索过 
			Tarjan(s);
			low[x] = min(low[x], low[s]);	// 更新所能到的上层节点 
		}
		else if(inS[s]){	// 在栈中 
			low[x] = min(low[x], DFN[s]);	// 到栈中最上端的节点 
		}
	}
	if(low[x] == DFN[x]){
		int y;
		while(1){
			y = S.top();
			inS[y] = 0;
			S.pop();
			K[y] = x;
			if(x == y)
				break;
			p[x] += p[y];
		}
	}
	return ;
}

int tuopu()		// 拓扑排序 
{
	queue<int> q;
	int tot = 0;
	for(int i = 1; i <= n; i++){
		if(K[i] == i && !in[i]){
			q.push(i);
			dis[i] = p[i];
		}
	}
	while(!q.empty()){
		int k = q.front();
		q.pop();
		for(int i = h[k]; i; i = edge2[i].next){
			int v = edge2[i].to;
			dis[v] = max(dis[v], dis[k] + p[v]);
			in[v]--;
			if(in[v] == 0)
				q.push(v);
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; i++)
		ans = max(ans, dis[i]);
	return ans;
}

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;
		add(u, v);
	}
	for(int i = 1; i <= n; i++){
		if(!DFN[i])
			Tarjan(i);
	}
	for(int i = 1; i <= m; i++){
		int x = K[edge1[i].from], y = K[edge1[i].to];
		if(x != y){
			edge2[++s].next = h[x];
			edge2[s].to = y;
			edge2[s].from = x;
			h[x] = s;
			in[y]++;
		}
	}
	cout << tuopu() << endl;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值