题目地址:
https://www.luogu.com.cn/problem/P3916
题目描述:
给出
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,\cdots,N
1,2,⋯,N编号。
输出格式:
N
N
N个整数
A
(
1
)
,
A
(
2
)
,
⋯
,
A
(
N
)
A(1),A(2),\cdots,A(N)
A(1),A(2),⋯,A(N)。
数据范围:
对于
60
%
60\%
60%的数据,
1
≤
N
,
M
≤
1
0
3
1 \le N, M \le 10^3
1≤N,M≤103;
对于
100
%
100\%
100%的数据,
1
≤
N
,
M
≤
1
0
5
1 \le N, M \le 10^5
1≤N,M≤105。
如果是无环图的话,则由于其能拓扑排序,可以按照拓扑排序的逆序向前递推,设 f [ u ] f[u] f[u]是 u u u能到达的编号最大的点的编号,则有: f [ u ] = max u → v { f [ v ] , u } f[u]=\max_{u\to v}\{f[v],u\} f[u]=u→vmax{f[v],u}但这种方法在这道题中是行不通的。如果有环,会有循环依赖。可以用Tarjan算法先求强联通分量,然后缩点,接着可以利用拓扑序逆序递推。但这道题还有更简单的做法,只需要建立反向图,然后依次从点 n , n − 1 , . . . , 1 n,n-1,...,1 n,n−1,...,1开始DFS,原图中每个点能到达的编号最大的点就成为了当前图里每个点能到达哪里,这样就很好做了。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int h[N], e[N], ne[N], idx;
int res[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u, int c) {
if (res[u]) return;
res[u] = c;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
dfs(v, max(v, c));
}
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(b, a);
}
for (int i = n; i; i--)
if (!res[i]) dfs(i, i);
for (int i = 1; i <= n; i++)
printf("%d ", res[i]);
}
时间复杂度 O ( n + m ) O(n+m) O(n+m),空间 O ( n ) O(n) O(n)。