[luogu p3916] 图的遍历

传送门

图的遍历

题目描述

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

输入输出格式

输入格式

第1 行,2 个整数\(N,M\)

接下来\(M\)行,每行2个整数\(U_i,V_i\),表示边\((U_i,V_i)\)。点用\(1, 2,\cdots,N\)编号。

输出格式

N 个整数\(A(1),A(2),\cdots,A(N)\)

输入输出样例

输入样例 #1

4 3
1 2
2 4
4 3

输出样例 #1

4 4 3 4

说明

  • 对于60% 的数据,\(1 \le N . M \le 10^3\)
  • 对于100% 的数据,\(1 \le N , M \le 10^5\)

分析

打眼一看,如果暴力算法,时间复杂度 \(\operatorname{O}(n^2)\)

\(10^5\) 这个数据量,平方算法肯定劝退了,要不然 \(\operatorname{O}(n\log n)\) 要不然 \(\operatorname{O}(n)\)\(\operatorname{O}(n\log n)\) 的时间复杂度算法我是没想出来,但是 \(\operatorname{O}(n)\) 的算法可以用逆向思维思考出。

什么是逆向思维呢?用本题举一个例子。本题让我们求每一个可以到达点编号最大的点,那么我们不仅要处理它可以到达的所有点,还要取最大值,而且一次处理只能处理一个,赔本买卖的结果就是TLE。

那么我们就可以逆向思维,思考编号大的点可以反向到达哪些点,这样,能到达的所有点如果之前没有被到达过,那么它的值肯定就是当前点编号了,一次设置,就再也不用变了。这样我们不仅可以一次处理就处理很多点,而且没有取最大值的操作,而且如果当前点之前就被到达,可以直接return,剪枝掉很多不必要的点。这样简直赚翻了。

具体实现过程是这样的:

  • 反向建边
  • 从N到1循环当前编号的点作为A点,并进行dfs
  • 可以dfs到的点B点中,如果B点的A值之前没有被设置,那就设置成A点的编号。如果B点的A值之前设置了,那么就直接return。(因为如果A点到C点的一条路径经过了B点,那么B点与C点就是联通的,那么既然B点被处理过了,C点也一定被处理过)
  • 处理完后,输出每个点的A值,结束程序

上代码:

代码

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-09-16 12:13:33 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-09-19 10:46:44
 */
#include <iostream>
#include <cstdio>
#include <vector>

const int maxn = 100005;

int A[maxn];
std :: vector <int> rev_side[maxn];

void dfs(int u, int num) {
    A[u] = num;
    for (int i = 0; i < rev_side[u].size(); ++i) {
        int v = rev_side[u][i];
        if (!A[v])
            dfs(v, num);
    }
}

int main() {
    int n, m;
    std :: scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int u, v;
        std :: scanf("%d %d", &u, &v);
        rev_side[v].push_back(u);
    }

    for (int i = n; i; --i) {
        if (!A[i])
            dfs(i, i);
    }
    
    for (int i = 1; i <= n; ++i)
        std :: printf("%d ", A[i]);
    putc('\n', stdout);
    return 0;
}

评测记录

评测记录

posted @ 2020-09-19 10:49  东北小蟹蟹  阅读(69)  评论(0编辑  收藏