洛谷 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 1≤N,M≤103。
- 对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 1 0 5 1 \leq N,M \leq 10^5 1≤N,M≤105。
—————————————————————————————————————
根据题目我们了解到,这个图可能会成环。对于每一个点出发能经过的最大编号,那一定是它所属的强连通分量中最大的编号,或者是从它所处的强连通分量出发,能到达的最大的编号的点。
所以我们可以先用 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;
}