leetcode 1579.保证图可完全遍历

leetcode 1579.保证图可完全遍历

题干

Alice 和 Bob 共有一个无向图,其中包含 n 个节点和 3  种类型的边:
类型 1:只能由 Alice 遍历。
类型 2:只能由 Bob 遍历。
类型 3:Alice 和 Bob 都可以遍历。
给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 ui 和 vi 之间存在类型为 typei 的双向边。请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。
返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1 。

示例 1:
输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,3],[1,2,4],[1,1,2],[2,3,4]]
输出:2
解释:如果删除 [1,1,2] 和 [1,1,3] 这两条边,Alice 和 Bob 仍然可以完全遍历这个图。再删除任何其他的边都无法保证图可以完全遍历。所以可以删除的最大边数是 2 。

示例 2:
输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,4],[2,1,4]]
输出:0
解释:注意,删除任何一条边都会使 Alice 和 Bob 无法完全遍历这个图。

示例 3:
输入:n = 4, edges = [[3,2,3],[1,1,2],[2,3,4]]
输出:-1
解释:在当前图中,Alice 无法从其他节点到达节点 4 。类似地,Bob 也不能达到节点 1 。因此,图无法完全遍历。

提示:
1 <= n <= 10^5
1 <= edges.length <= min(10^5, 3 * n * (n-1) / 2)
edges[i].length == 3
1 <= edges[i][0] <= 3
1 <= edges[i][1] < edges[i][2] <= n
所有元组 (typei, ui, vi) 互不相同

题解

首先明确什么情况下可以完全遍历一个题中所谓的图。
显然只要保证图连通(对Alice和Bob而言),这个图就是可以完全遍历的。
考虑到Alice能走的路有第一类和第三类边,Bob能走的路有第二类和第三类边,于是想办法从第三类边入手,来考察图的连通性。
于是先统计第三类边所能构成的所有连通分支,并计算对于第三类边的连通分支能够精简多少边(对于一个连通分支来说,可精简的边数一定是:组成连通分支的边数 - (连通分支的结点数 - 1),意即最少需要节点数 - 1条边就可以使连通分支连通)
在统计第三类边能够成的连通分支的同时,运用并查集路径压缩的方法将连通分支内所有结点的祖先置为连通分支内编号最小的结点(比如结点2 3 6 11组成连通分支,则ancestor[2] = ancestor[3]=ancestor[6]=ancestor[11]=2
然后分别考察Alice和Bob特有的第一类边和第二类边,对于第一类边和第二类边来说,他们的任务是把第三类边构成的所有连通分支串起来使不同的连通分支连通。
如此,可以遍历所有第一/二类边,对于遍历到的每条边的两个端点,获取两个端点的最大祖先,如果两个最大祖先不同,则说明两个端点位于两个不同的连通分支,即这条边可以将两个连通分支连通。如此可以统计所有第一/二类边共能串其多少个连通分支,并将这个数字和之前统计出的连通分支数比较。
若第一/二类边所能连通的连通分支数少于原有的连通分支,则说明有人无法遍历整张图。
而显然,能遍历整张图的情况下,答案一定是确定的:
每个连通分支内部可精简的边数 +(第一类边数 - (连通分支数 - 1)) + (第二类边数 - (连通分支数 - 1))

class Solution {
public:

    vector<int> ancestor;

    int findAncestor(int n){
        if(ancestor[n] == -1){
            return -1;
        }
        while(ancestor[n] != n){
            n = ancestor[n];
        }
        return n;
    }

    int maxNumEdgesToRemove(int n, vector<vector<int>>& edges) {
        ancestor.resize(100005,-1);
        unordered_map<int,int> ancestorSet;
        int componentsUnderTypeThreeEdge = 0;
        int typeOneEdgeCount = 0;
        int typeTwoEdgeCount = 0;
        int ans = 0;
        for(auto i : edges){
            if(i[0] == 3){
            //处理第三类边
                if(findAncestor(i[1]) == -1 && findAncestor(i[2]) == -1){
                //若该边连接的两个节点都还没有祖先,将i[1]设为自己的祖先,也设为i[2]的祖先,同时记录i[1]为一新连通分支的祖先
                    ancestor[i[1]] = i[1];
                    ancestor[i[2]] = i[1];
                    componentsUnderTypeThreeEdge++;
                    ancestorSet[i[1]] = i[1];
                }else if(findAncestor(i[1]) != -1 && findAncestor(i[2]) == -1){
                //i[1]已有祖先,但i[2]没有祖先的情况,将i[2]的祖先设为i[1]的祖先
                    ancestor[i[2]] = findAncestor(i[1]);
                }else if(findAncestor(i[1]) == -1 && findAncestor(i[2]) != -1){
                //i[1]没有祖先,但i[2]有祖先的情况,将i[1]的祖先设为i[2]的祖先
                    ancestor[i[1]] = findAncestor(i[2]);
                }else if(findAncestor(i[1]) != -1 && findAncestor(i[2]) != -1 && findAncestor(i[1]) != findAncestor(i[2])){
                //i[1]和i[2]都有祖先,但是两者祖先不相同的情况,即打通了两个连通分支,将i[2]的最上级祖先的祖先设为i[1]的最上级祖先
                    ancestor[findAncestor(i[2])] = findAncestor(i[1]);
                    componentsUnderTypeThreeEdge--;
                }else{
                //i[1]和i[2]有共同祖先,说明两节点已连通,此时边可舍去不要
                    ans++;
                }
            }else if(i[0] == 2){
                typeTwoEdgeCount++;
            }else if(i[0] == 1){
                typeOneEdgeCount++;
            }
        }

        for(int i = 0 ; i < n ; ++i){
            if(ancestor[i+1] == -1){
            //遍历所有节点,如果存在节点的祖先仍为-1,说明仅用第三类边连接的情况下,该节点为孤立节点
                componentsUnderTypeThreeEdge++;
            }
        }

        if(typeOneEdgeCount < componentsUnderTypeThreeEdge - 1 || typeTwoEdgeCount < componentsUnderTypeThreeEdge - 1){
            return -1;
        }

        //如果存在答案,答案一定是第三类边连通分支内可精简的条数加上第一类第二类边数减去第三类边连通分支数-1
        //ans = ans + typeOneEdgeCount + typeTwoEdgeCount - (componentsUnderTypeThreeEdge - 1) * 2;
        //所以在返回答案前还需检查第一类边和第二类边是否连通了全部第三类边连通分支。
        vector<int> ancestorBackUp(ancestor);
        int typeOneEdgeEffectiveCount = 0;
        int typeTwoEdgeEffectiveCount = 0;
        
        for(auto i : edges){
            if(i[0] == 1){
            //处理第一类边
            //如果边连接的两个节点不属于一个连通分支,这个边才是有效的
                if(findAncestor(i[1]) == -1 && findAncestor(i[2]) != -1){
                //左边节点是孤立节点,而右边节点属于一个连通分支
                    ancestor[i[1]] = findAncestor(i[2]);
                    typeOneEdgeEffectiveCount++;
                }else if(findAncestor(i[1]) != -1 && findAncestor(i[2]) == -1){
                //右边节点是孤立节点,而左边节点属于一个连通分支   
                    ancestor[i[2]] = findAncestor(i[1]);
                    typeOneEdgeEffectiveCount++;     
                }else if(findAncestor(i[1]) == -1 && findAncestor(i[2]) == -1){
                //两个节点都是孤立节点
                    ancestor[i[1]] = i[1];
                    ancestor[i[2]] = i[1];
                    typeOneEdgeEffectiveCount++;     
                }else if(findAncestor(i[1]) != findAncestor(i[2]) ){
                //两个节点都各自属于一个更大的连通分支
                    ancestor[findAncestor(i[2])] = findAncestor(i[1]);
                    typeOneEdgeEffectiveCount++;
                }
            }
        }
        if(typeOneEdgeEffectiveCount < componentsUnderTypeThreeEdge - 1){
            return -1;
        }

        ancestor.swap(ancestorBackUp);

        for(auto i : edges){
            if(i[0] == 2){
            //处理第二类边
            //如果边连接的两个节点不属于一个连通分支,这个边才是有效的
                if(findAncestor(i[1]) == -1 && findAncestor(i[2]) != -1){
                //左边节点是孤立节点,而右边节点属于一个连通分支
                    ancestor[i[1]] = findAncestor(i[2]);
                    typeTwoEdgeEffectiveCount++;
                }else if(findAncestor(i[1]) != -1 && findAncestor(i[2]) == -1){
                //右边节点是孤立节点,而左边节点属于一个连通分支   
                    ancestor[i[2]] = findAncestor(i[1]);
                    typeTwoEdgeEffectiveCount++;     
                }else if(findAncestor(i[1]) == -1 && findAncestor(i[2]) == -1){
                //两个节点都是孤立节点
                    ancestor[i[1]] = i[1];
                    ancestor[i[2]] = i[1];
                    typeTwoEdgeEffectiveCount++;     
                }else if(findAncestor(i[1]) != findAncestor(i[2])){
                //两个节点都各自属于一个更大的连通分支
                    ancestor[findAncestor(i[2])] = findAncestor(i[1]);
                    typeTwoEdgeEffectiveCount++;
                }
            }
        }

        if(typeTwoEdgeEffectiveCount < componentsUnderTypeThreeEdge - 1){
            return -1;
        }
        
        return ans + typeOneEdgeCount + typeTwoEdgeCount - (componentsUnderTypeThreeEdge - 1) * 2;
    }
    
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值