P3387 【模板】缩点 TJ

题目链接

思路

模板题,先使用 (Tarjan) 进行缩点,然后重新建边,最后拓扑排序即可

Tarjan

强连通分量

在有向图G<V,E>中,对于点集V'∈V, 点集中的任意两点都可达,则称V'为强连通

Tarjan

在有向图中,(DFS) 时,我们需要维护 (dfn)(low),目的在于把所有的强连通分量缩成一个点(因为题目里说可以重复走,而强连通分量里两点都可以到达)
(dfn_i) 为遍历到当前节点 (i) 的时间戳,(low_i) 为当前节点 (i) 能回溯到的最早节点,
(dfn_i = low_i) 时说明以 (i) 为根形成了强连通分量,
此时我们可以用栈来维护当前强连通分量里的所有点。

拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。

此时缩点后的图是一个有向无环图(强连通分量被缩成点),我们考虑使用拓扑排序求出最终的最长路径
先将入度为 (0) 的点进入队列,然后出队,将可到达的点的入度减 (1) ,然后将当前入度为 (0) 点加入队列,过程中可以 (DP) 求出最长路径

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int MAXN = 1e4 + 10;
const int MAXM = 1e5 + 10;
queue <int > s;

int sta[MAXN] ,ind = 0;

vector <int > c[MAXN];
int in[MAXN];

struct node {
	int next, to ,from;
}edge[MAXM];
int head[MAXN] ,cnt = 0;
void add (int from ,int to) {
	edge[++ cnt].next = head[from];
	head[from] = cnt;
	edge[cnt].to = to;
	edge[cnt].from = from;
}

int n ,m ,answ = 0;
int dis[MAXN] ,dis_[MAXN];

int tot = 0 ,times = 0;
int dfn[MAXN] ,low[MAXN] ,ins[MAXN] = {0} ,vis[MAXN];
void tarjan (int u) {
	dfn[u] = low[u] = ++ times;//初始dfn和low均为时间戳 
	sta[++ ind] = u ,ins[u] = 1;//进栈,手写 
	for (int q = head[u];~ q;q = edge[q].next) {//dfs 
		int v = edge[q].to;
		if (! dfn[v]) {//向下遍历 
			tarjan (v);
			low[u] = min (low[u] ,low[v]);
		}
		else if (ins[v])
			low[u] = min (low[u] ,dfn[v]);
	}
	if (low[u] == dfn[u]) {//找到强连通分量 
		tot ++;//当前强连通分量 的 新的节点编号(因为缩成一个点了)
		while (true) {
			vis[sta[ind]] = tot;
			dis_[tot] += dis[sta[ind]];//将路径长度累加到点上 
			ins[sta[ind]] = 0; ind --;//出栈 
			if (u == sta[ind + 1])//当前到根,退出 
				break;
		}
	}
}
int tot_ = 0;
int dp[MAXN];
void toposort () {
	for (int q = 1;q <= tot;++ q) {//预处理入度为0的节点 
		if (in[q] == 0) {
			s.push(q);
			dp[q] = dis_[q];
		}
	}
	while (! s.empty()) {
		int u = s.front();
		s.pop();
		for (int q = 0;q < c[u].size();++ q) {//向下遍历 
			int v = c[u][q];
			in[v] --;//减去1 
			dp[v] = max (dp[v] ,dp[u] + dis_[v]);//DP求出最长路 
			if (in[v] == 0) {//将当前入度为0的节点进队 
				s.push(v);
				answ = max (answ ,dp[v]);
			}
		}
	}
}

int main () {
	memset (head ,-1 ,sizeof (head));
	memset (in ,0 ,sizeof (in));
	scanf ("%d%d",&n ,&m);
	for (int q = 1;q <= n;++ q)
		scanf ("%d",dis + q);
	int from_ ,to_;
	for (int q = 1;q <= m;++ q) {//初始边,此处链式前向星 
		scanf ("%d%d",&from_ ,&to_);
		add (from_ ,to_);
	}
	for (int q = 1;q <= n;++ q)//tarjan
		if (! dfn[q]) tarjan (q);
	for (int q = 1;q <= m;++ q) {//重新建边(缩点后) ,此处用邻接表 
		if (vis[edge[q].from] != vis[edge[q].to]) {
			from_ = vis[edge[q].from] ,to_ = vis[edge[q].to];
			c[from_].push_back(to_);
			in[to_] ++;
		}
	}
	toposort ();//拓扑排序 
	for (int q = 1;q <= tot;++ q)
		answ = max (answ ,dp[q]);
	printf ("%d
",answ);
	return 0;
}

cb
原文地址:https://www.cnblogs.com/tuscjaf/p/13831018.html