【Tarjan】【强连通分量】有向图缩点

链接

有向图缩点

题目描述

给定一个n个点m条边的有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是重复经过的点,权值只计算一次。

样例输入

2 2
1 1
1 2
2 1

样例输出

2

思路

这题我们可以把能够互相到达的点缩成一个点,然后把点权统计起来,再与别的缩点连边
那么这个求可缩的点就是要求强连通分量
我们用Tarjan算法来实现
记录 d f n i dfn_i dfni表示i这个点的dfs序
再设 l o w i low_i lowi表示i所在强连通分量的dfn值最小的点,也就是这个强连通分量的代表点
最后设 c o l i col_i coli表示i这个点所在的强连通分量的编号(也是缩点的编号
遍历到x点的时候
我们找它的所连点,然后一个个求遍历,并更新low值,若有一个点的dfn值和low值相等,则此点为所在强连通分量的代表,以此点编强连通分量的号码
以上只是简单带过
详见Tarjan讲解
缩点之后再想一下怎么求最大值
给出一个有向无环图,然后每个点有点权,我想到用拓扑排序外加DP去求最大值

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring> 
#include<queue>

using namespace std;

int n, m, ans, t, tt, top, p, Tt;
int h[100007], hh[100007];
int sum[100007], a[100007], ru[100007], f[100005];
int col[100005], s[100005], dfn[100005], low[100005];

struct q
{
	int to, next; 
}gg[1000005];

struct node
{
	int from, to, next;
}g[1000005];

void addx(int u, int v)
{
	gg[++tt] = (q){v, hh[u]}; hh[u] = tt;
}

void add(int u, int v)
{
	g[++t] = (node){u, v, h[u]}; h[u] = t;
}

void Tarjan(int x)
{
	dfn[x] = low[x] = ++Tt;
	s[++top] = x;
	for(int i = h[x]; i; i = g[i].next)
	{
		int to = g[i].to;
		if(!dfn[to])
		{
			Tarjan(to);
			low[x] = min(low[x], low[to]);
		}
		else if(!col[to]) low[x] = min(low[x], low[to]);
	}
	if(dfn[x] == low[x]) {
		col[x] = ++p;
		sum[p] += a[x];
		while(s[top] != x)//在x后入栈的点都处在x的强连通分量中(因为是x的连边过去的)
			col[s[top]] = p, sum[p] += a[s[top--]];
		top--;
	}
}

void dp()
{
	queue<int>Q;
	for(int i = 1; i <= p; ++i)
		if(!ru[i]) Q.push(i), f[i] = sum[i];
	while(Q.size())
	{
		int tot = Q.front();
		Q.pop();
		for(int i = hh[tot]; i; i = gg[i].next)
		{
			int to = gg[i].to;
			f[to] = max(f[to], f[tot] + sum[to]);
			ru[to]--;
			if(!ru[to]) Q.push(to);
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i)
		scanf("%d", &a[i]);
	for(int i = 1; i <= m; ++i)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v);
	}
	for(int i = 1; i <= n; ++i)
		if(!dfn[i]) Tarjan(i);
	for(int i = 1; i <= t; ++i)
	{
		int to = g[i].to, from = g[i].from;
		if(col[to] != col[from])//to和from所在的强连通分量不一样
		{
			addx(col[from], col[to]);
			ru[col[to]]++;
		}
	}//构建新图
	dp();
	for(int i = 1; i <= p; ++i)
		ans = max(ans, f[i]);
	printf("%d", ans);
	return 0; 
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值