洛谷 P3916 图的遍历(tarjan + 缩点 + dfs)

洛谷 P3916 图的遍历(tarjan + 缩点 + dfs)

放个传送门

这道题其实很多人都选择反向建图,然后dfs一下就过了。但确是一道不错的 t a r j a n tarjan tarjan + 缩点 + d f s dfs dfs 的练手题
————————————————————————————————————

图的遍历

题目描述

给出 N N N 个点, M M M 条边的有向图,对于每个点 v v v,求 A ( v ) A(v) A(v) 表示从点 v v v 出发,能到达的编号最大的点。

输入格式

1 1 1 2 2 2 个整数 N , M N,M N,M,表示点数和边数。

接下来 M M M 行,每行 2 2 2 个整数 U i , V i U_i,V_i Ui,Vi,表示边 ( U i , V i ) (U_i,V_i) (Ui,Vi)。点用 1 , 2 , … , N 1,2,\dots,N 1,2,,N 编号。

输出格式

一行 N N N 个整数 A ( 1 ) , A ( 2 ) , … , A ( N ) A(1),A(2),\dots,A(N) A(1),A(2),,A(N)

样例 #1

样例输入 #1

4 3
1 2
2 4
4 3

样例输出 #1

4 4 3 4

提示

  • 对于 60 % 60\% 60% 的数据, 1 ≤ N , M ≤ 1 0 3 1 \leq N,M \leq 10^3 1N,M103
  • 对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 1 0 5 1 \leq N,M \leq 10^5 1N,M105

—————————————————————————————————————
根据题目我们了解到,这个图可能会成环。对于每一个点出发能经过的最大编号,那一定是它所属的强连通分量中最大的编号,或者是从它所处的强连通分量出发,能到达的最大的编号的点。

所以我们可以先用 t a r j a n tarjan tarjan 跑出来每个点所属的强连通分量,并且跑出来每个强连通分量中最大的编号是什么。

然后我们可以将每个强连通分量缩成一个点来看,再重新建图,跑一个 d f s dfs dfs ,对比是所属强连通分量中最大的编号大,还是能到达的另一个强连通分量的编号大。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int M = 1e5 + 10;
const ll inf = 4e18 + 5;
const ll P = 1e9 + 7;

ll n, m;
vector<ll>g[M], ltg[M];//g是原图,ltg是强连通分量图

ll ans[M];

//tarjan老伙伴
ll low[M];
ll dfn[M];
ll idx = 1;//时间序
stack<ll>stk;//按dfs遍历顺序存储节点
bitset<M>vis;

ll rep[M];//储存下标所代表的强连通分量中的最大编号
ll number = 0;//用于给强连通分量标号
ll belong[M];//看当前节点处于哪个连通分量

void tarjan(int x)
{
	low[x] = dfn[x] = ++idx;
	stk.push(x);
	vis[x] = 1;

	for (auto& y : g[x])
	{
		if (dfn[y] == 0)
		{
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}
		else if (vis[y])
			low[x] = min(low[x], dfn[y]);
	}

	if (low[x] == dfn[x])
	{
		number++;
		while (stk.top() != x)
		{
			ll ttt = stk.top();
			stk.pop();

			vis[ttt] = 0;
			belong[ttt] = number;
			rep[number] = max(rep[number], ttt);
		}

		ll ttt = stk.top();
		stk.pop();

		vis[ttt] = 0;
		belong[ttt] = number;
		rep[number] = max(rep[number], ttt);
	}

}

void rebuild()
{
	for (int i = 1; i <= n; i++)
		for (auto& y : g[i])
			if (belong[i] != belong[y])//如果不是一个强连通分量,再加入新图
				ltg[belong[i]].push_back(belong[y]);
}


void dfs(int x)
{
	if (ans[x])
		return;
	ans[x] = rep[x];//要么是自己所属强连通分量中的最大编号

	//一定注意先dfs,回退的时候再取max
	for (auto& y : ltg[x])
	{
		dfs(y);
		ans[x] = max(ans[x], ans[y]);//要么是从自己所属强连通分量出发能到达的最大编号
	}

}

void solve()
{
	cin >> n >> m;

	for (int i = 1; i <= m; i++)
	{
		ll x, y;
		cin >> x >> y;

		g[x].push_back(y);
	}

	//先跑tarjan确定每个点的所属强连通分量
	for (int i = 1; i <= n; i++)
	{
		if(!dfn[i])
			tarjan(i);
	}

	//根据强连通分量的缩点,建立新图
	rebuild();
	
	//根据缩点后的新图,跑dfs
	for (int i = 1; i <= number; i++)
	{
		if (!ans[i])
			dfs(i);
	}

	for (int i = 1; i <= n; i++)
		cout << ans[belong[i]] << ' ';
}

int main()
{
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int T = 1;
	//cin >> T;
	while (T--)
		solve();
	return 0;
}
  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值