网易2020/8/8笔试题(四)

第一题地址:网易2020/8/8笔试题(一)

第二题地址:网易2020/8/8笔试题(二)

第三题地址:网易2020/8/8笔试题(三)

接上篇博客,网易笔试题第四题:学术认可。

高校中有n个教授,可以在评定中每个教授可以投出自己认可的教授,每个教授可以投多个人,也可以投自己。

规定若A认可B,B认可C,则A也认可C。

问有多少对教授是互相认可的?

输入为n(教授个数),m(认可的关系数),以及m组数字,每组数字代表一个单向认可的关系,如1 3表示教授1认可教授3。

对于输入案例:

5

6

1    3

2    1

3    2

3    5

4    5

5    4

则输出应为4,因为教授1与教授2互相认可,教授1与教授3互相认可,教授2与教授3互相认可,教授4与教授5互相认可。因此共有4对教授是互相认可的。

对于此问题可以转化为在一个有向图中求强连通分量,然后统计强连通分量中去重边的数量。

 

可以使用tarjan算法来求解此问题,在放代码前先简单的解释一下相关知识。

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

Tarjan算法思路不难理解,因为任何一个强连通分量,必定是对原图的深度优先搜索树的子树。那么其实只要确定每个强连通分量的子树的根,然后根据这些根从树的最低层开始,一个一个的拿出强连通分量即可。那么剩下的问题就只剩下如何确定强连通分量的根和如何从最低层开始拿出强连通分量了。

在Tarjan算法中,有如下定义。

DFN[ i ] : 在DFS中该节点被搜索的次序(时间戳)

LOW[ i ] : 为i或i的子树能够追溯到的最早的栈中节点的次序号

当DFN[ i ]==LOW[ i ]时,为i或i的子树可以构成一个强连通分量。

而为了存储整个强连通分量,这里挑选的容器是堆栈。每次一个新节点出现,就进栈,如果这个点有出度就继续往下找。直到找到底,每次返回上来都看一看子节点与这个节点的LOW值,谁小就取谁,保证最小的子树根。如果找到DFN[]==LOW[]就说明这个节点是这个强连通分量的根节点(毕竟这个LOW[]值是这个强连通分量里最小的。)最后找到强连通分量的节点后,就将这个栈里,比此节点后进来的节点全部出栈,它们就组成一个全新的强连通分量。

关于Tarjan以及强连通分量的更具体的内容本篇博客就不再赘述了,需要详细了解的同学可以参考网上的一些优值文章,都有很详细的介绍以及案例演示。

 

那么接下来就是用Tarjan算法求解题目了,注意对于每个强连通分量,两两认可的个数为:(n-1)*n/2,即排列组合中的Cn2,n为当前强连通分量的节点数。

Talk is cheap, show me the code.

 

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Stack;

public class NETest4 {
    static int count = 0;
    static Stack stack = new Stack<>();
    static int[] dfn;
    static int[] low ;
    static boolean[] inStack;   //看当前节点是否在栈中
    static ArrayList[] graph;
    static ArrayList> result = new ArrayList<>();

    static class edge {
        public int u;  //边的起点
        public int v;  //边的终点
        edge(int u, int v) {
            this.u = u;
            this.v = v;
        }
    }

    private static void tarjan(ArrayList[] graph, int cur){
        dfn[cur] = low[cur] = count++;
        inStack[cur] = true;
        stack.push(cur);
        int next = cur;
        for (int i = 0; i < graph[cur].size(); i++) {
            next = graph[cur].get(i).v;
            if (dfn[next] == -1){
                tarjan(graph, next);
                low[cur] = Math.min(low[cur], low[next]);
            }else if (inStack[next]){
                low[cur] = Math.min(low[cur], dfn[next]);
            }
        }
        if (dfn[cur] == low[cur]){
            ArrayList temp = new ArrayList<>();
            while(cur != next){
                next = stack.pop();
                temp.add(next);
                inStack[next] = false;
            }
            result.add(temp);
        }
        return;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int proNum = sc.nextInt();
        int relNum = sc.nextInt();
        dfn = new int[proNum];
        low = new int[proNum];
        inStack = new boolean[proNum];
        Arrays.fill(dfn, -1);   //-1表示节点未被遍历
        Arrays.fill(low, -1);
        Arrays.fill(inStack, false);
        graph = new ArrayList[proNum];
        for (int i = 0; i < proNum; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int i = 0; i < relNum; i++) {
            int u = sc.nextInt() - 1;   //因为输入的节点是从1开始的,而数组是0开始的,因此把教授编号也转化为从0开始。
            int v = sc.nextInt() - 1;
            graph[u].add(new edge(u, v));
        }
        tarjan(graph, 1);
        int res = 0;
        for (ArrayList list : result) {
            int num = list.size();
            res += (num - 1) * num / 2;     //每个强连通分量中两两认可的个数为(n-1)*n/2
        }
        System.out.println(res);
    }
}

至此,网易8月8日算法的笔试题全部结束。

多做复盘,搞懂每个题,每日进步。

你可能感兴趣的:(算法笔试,算法,网易,图论)