【算法练习】leetcode每日一题/并查集 1579. 保证图可完全遍历

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 都可以使用的,而他们各自的独占边却不能给对方使用。「公共边」的重要性也是可以证明的:

对于一条连接了两个不同的连通分量的「公共边」而言,如果我们不保留这条公共边,那么 Alice 和 Bob 就无法往返这两个连通分量,即他们分别需要使用各自的独占边。因此,Alice 需要一条连接这两个连通分量的独占边,Bob 同样也需要一条连接这两个连通分量的独占边,那么一共需要两条边,这就严格不优于直接使用一条连接这两个连通分量的「公共边」了。

因此,我们可以遵从优先添加「公共边」的策略。具体地,我们遍历每一条「公共边」,对于其连接的的两个节点:

如果这两个节点在同一个连通分量中,那么添加这条「公共边」是无意义的;

如果这两个节点不在同一个连通分量中,我们就可以(并且一定)添加这条「公共边」,然后合并这两个节点所在的连通分量。

这就提示了我们使用并查集来维护整个图的连通性,上述的策略只需要用到并查集的「查询」和「合并」这两个最基础的操作。

在处理完了所有的「公共边」之后,我们需要处理他们各自的独占边,而方法也与添加「公共边」类似。我们将当前的并查集复制一份,一份交给 Alice,一份交给 Bob。随后 Alice 不断地向并查集中添加「Alice 独占边」,Bob 不断地向并查集中添加「Bob 独占边」。在处理完了所有的独占边之后,如果这两个并查集都只包含一个连通分量,那么就说明 Alice 和 Bob 都可以遍历整个无向图。

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable/solution/bao-zheng-tu-ke-wan-quan-bian-li-by-leet-mtrw/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

写一下class 的并查集模板  (没有维护

初始化parent[i]=-1

class UnionFind{
public:
    vector<int> parent;
    int n;
    int setCount;//当前连通分量的个数
public:
    UnionFind(int n_):n(n_),parent(n_+1,-1),setCount(n_){}   //从1开始编号 所以n+1  初始化是-1

    int findRoot(int x){
        if(parent[x]==-1){
            return x;
        }
        else{
            int tmp=findRoot(parent[x]);
            parent[x]=tmp;
            return tmp;
        }
    }

    bool unite(int x,int y){
        x=findRoot(x);    //这个地方千万要注意是findRoot
        y=findRoot(y);
        if(x==y){
            return false;
        }
        else{
            parent[x]=y;
            //注意这个部分 合并之后连通分量就减少了
            setCount--;
            return true;
        }
    }
};

 

 

代码:

#include<iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <map>
using namespace std;
// 并查集模板
class UnionFind{
public:
    vector<int> tree;
    int setCount;
    int n;

    UnionFind(int _n):tree(_n+1,-1),setCount(_n),n(_n){}  //构造函数
    int findRoot(int x){
        if(tree[x]==-1){
            return x;
        }
        else{
            int tmp=findRoot(tree[x]);
            tree[x]=tmp;
            return tmp;
        }
    }
    bool unite(int x,int y){
        x=findRoot(x);
        y=findRoot(y);
        if(x==y){
            return false;
        } else{
            tree[x]=y;
            setCount--;
            return true;
        }
    }
};


int maxNumEdgesToRemove(int n, vector<vector<int>>& edges) {
  UnionFind ufa(n),ufb(n);
  int xx,yy;
  int ans=0;  //记录需要删除的边
  for(int i=0;i<edges.size();i++){
        //先处理公共边
        if(edges[i][0]==3){
            xx=edges[i][1];
            yy=edges[i][2];
            if(!ufa.unite(xx,yy)){  //没有发生合并 就是这条边多余了
                ans++;
            }
            else{  //发生了合并 b的并查集也做相同的合并
                ufb.unite(xx,yy);
            }

        }
    }
  //处理单独边
  for(int i=0;i<edges.size();i++){
      //A
        if(edges[i][0]==1){
            xx=edges[i][1];
            yy=edges[i][2];
            if(!ufa.unite(xx,yy)){  //没有发生合并 就是这条边多余了
                ans++;
            }
        }

        //B
      if(edges[i][0]==2){
          xx=edges[i][1];
          yy=edges[i][2];
          if(!ufb.unite(xx,yy)){  //没有发生合并 就是这条边多余了
              ans++;
          }
      }
  }
  if(ufa.setCount!=1 || ufb.setCount!=1){
      return -1;
  }
  return ans;
}


int main(){
    vector<vector<int>> edges={{3,1,2},{3,2,3},{1,1,3},{1,2,4},{1,1,2},{2,3,4}};
    cout<<maxNumEdgesToRemove(4,edges);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值