LeetCode 2127-参加会议的最多员工数

文章探讨了如何在员工间形成的关系图中,利用动态规划和拓扑排序算法确定最大邀请数量,规则是基于环状结构和基环内向树的特性,确保每个人喜欢的人在其附近。
摘要由CSDN通过智能技术生成

在这里插入图片描述

前言

如果我们把每个员工看成图上的一个节点,员工 x x x 喜欢员工 y y y,就在从 x x x 对应的节点到 y y y 对应的节点连一条边,那么形成的图则会由若干棵 基环内向树 组成。所谓 基环内向树 就是形如下图所示的结构:
在这里插入图片描述
原因如下:

  • 从任意一个节点 x x x 开始在图上进行 游走,由于每个员工只有一位喜欢的员工,因此每个节点在图上只有一条出边,即 游走 的过程是唯一的。由于图上有 n n n 个节点,因此在 n + 1 n+1 n+1 步以内,一定会走到一个重复的节点,那么在第一次经过该节点之后,到第二次经过该节点之前的所有节点及该节点本身就组成了一个环,如上图蓝色节点所示。
  • 对于不在环上的节点,我们已经说明了从它们开始 游走 也一定会进入到环中。到达环上的节点之前,它们不会重复经过节点(否则就有两个环了,我们可以证明一个连通分量中不可能有两个环:因为每个节点只有一条出边,因此如果有两个环并且它们连通,那么必然某个环上有一个点有两条出边,一条出边指向同一个环上的节点,另一条出边可以使它到达另一个环,这就产生了矛盾),那么它们就形成了类似树的结构,如上图绿色节点所示。

方法一:动态规划+拓扑排序

既然我们知道了图由若干棵 基环内向树 组成,那么我们就可以想一想,每一棵 基环内向树 的哪一部分可以被安排参加会议?

我们首先讨论特殊的情况,即一个单独的环(或若干个环),并且所有环的大小都 ≥ 3 \ge3 3。可以发现,我们按照环上的顺序给对应的员工安排座位是满足要求的,因为对于每一个环上的员工,它喜欢的员工就在它旁边。并且,我们必须安排环上的所有员工,因为如果有缺失,那么喜欢那位缺失了的员工就无法满足要求了。

但如果我们已经安排了某一个环上的所有员工,剩余的环就没办法安排了。这是因为已经安排的那个环是没办法被断开的:断开的本质就是相邻位置员工的缺失。因此,我们可以得出一个重要的结论:如果我们想安排大小 ≥ 3 \ge3 3 的环,我们最多只能安排一个,并且环需要是完整的

那么如果是环大小 ≥ 3 \ge3 3基环内向树 呢?如果我们安排了不在环上的节点,那么从该节点开始,我们需要不断安排当前节点喜欢的员工,这实际上就是 游走 的过程,而当我们游走到环上最后一个未经过的节点时,该节点的下一个节点(即喜欢的员工)已经被安排过,所以最后一个未经过的节点就无法被安排,不满足要求。因此,我们不能安排任何不在环上的节点,只能安排在环上的节点,就得出了另一个结论:所有环 ≥ 3 \ge3 3基环内向树 与一个大小相同(指环的部分)的环是等价的

那么最后我们只需要考虑大小 = 2 = 2 =2 的环或者 基环内向树 了。这里的特殊之处在于,大小 = 2 =2 =2 的环可以安排多个 :因为环上的两个点是 互相喜欢 的,因此只需要它们相邻即可。而对于环大小 = 2 =2 =2基环内向树,如果我们安排了不在环上的节点,那么游走完环上两个节点之后,同样是满足要求的,并且我们甚至可以继续延伸(反向 游走),到另一个不在环上的节点为止。如图所示,包含 x x x 的节点就是可以安排参加会议的节点。
在这里插入图片描述
并且同样地,对于每一棵环大小 = 2 =2 =2基环内向树,我们都可以取出这样一条 双向游走 路径进行安排,它们之间不会影响。综上所述,原问题的答案即为下面二者中的最大值:

  • 最大环的大小
  • 所有环大小 = 2 =2 =2基环内向树 上的最长的 双向游走 路径之和

为了求解 基环内向树 上最长的 双向游走 路径,我们可以使用 拓扑排序+动态规划 的方法。记 f [ i ] f[i] f[i] 表示到节点 i i i 为止的最长 游走 路径经过的节点个数,那么状态方程即为: f [ i ] = max ⁡ j → i { f [ j ] } + 1 f[i]=\max_{j\rightarrow i}\{f[j]\} + 1 f[i]=jimax{f[j]}+1即我们考虑节点 i i i 的上一个节点 j j j,在图中必须有从 j j j i i i 的一条有向边,这样我们就可以从 j j j 转移到 i i i。如果不存在满足要求的 j j j(例如 基环内向树 退化成一个大小 = 2 =2 =2 的环),那么 f [ i ] = 1 f[i]=1 f[i]=1。状态转移和拓扑排序可以同时进行。

在拓扑排序完成后,剩余没有被弹出过队列的节点就是环上的节点。我们可以找出每一个环。如果环的大小 ≥ 3 \ge 3 3,我们就用其来更新最大的环的大小;如果环的大小 = 2 =2 =2,设环上的两个节点为 x x x y y y,那么该 基环内向树 上最长的 双向游走 的路径长度就是 f [ x ] + f [ y ] f[x]+f[y] f[x]+f[y]

class Solution {
    public int maximumInvitations(int[] favorite) {
        int n = favorite.length;
        int[] indeg = new int[n];            //统计入度,便于进行拓扑排序
        for(int i = 0; i < n; i++){
            ++indeg[favorite[i]];
        }
        boolean[] used = new boolean[n];
        int[] f = new int[n];               //f[i]表示到节点i为止的最长路径经过的节点个数
        Arrays.fill(f, 1);
        Queue<Integer> queue = new ArrayDeque<Integer>();
        for(int i = 0; i < n; i++){         //寻找入度为0的节点,并入队
            if(indeg[i] == 0){
                queue.offer(i);
            }
        }
        while(!queue.isEmpty()){            //拓扑排序+动态规划:求到节点i为止的最长路径经过的节点个数
            int u = queue.poll();
            used[u] = true;
            int v = favorite[u];
            f[v] = Math.max(f[v], f[u] + 1);//状态转移
            --indeg[v];                     //v节点入度减1
            if(indeg[v] == 0){              //若入度为0,则入队
                queue.offer(v);
            }
        }
        int ring = 0;                       //表示最大环的大小
        int total = 0;                      //表示所有环大小为2的基环内向树树上的最长的双向游走路径之和
        for(int i = 0; i < n; ++i){
            if(!used[i]){
                int j = favorite[i];
                if(favorite[j] == i){       //说明环的大小为2
                    total += f[i] + f[j];
                    used[i] = used[j] = true;
                }else{                      //否则环的大小至少为3,我们需要找出环
                    int u = i, cnt = 0;
                    while(true){            //求环的大小
                        ++cnt;
                        u = favorite[u];
                        used[u] = true;
                        if(u == i){
                            break;
                        }
                    }
                    ring = Math.max(ring, cnt);
                }
            }
        }
        return Math.max(ring, total);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值