题目描述
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) 互不相同
分析
本题也是被标记为hard的一题,但是没啥难度,用例都不用看根据题意就可以推出解法,可以用来回忆下最小生成树的知识。
图中的边有三类,第一类只能A走,第二类只能B走,第三类两个都可以走,求删除最少的边使得两个人仍可以走遍整个图。换句话说就是在原图中选择最少的边,使得图依旧连通,也就是最小生成树问题。与常规的最小生成树问题的区别在于边的类别,也就是即使a到b之间有一条第一类边,对于B来说这两个点依旧是不连通的。
回忆下解决最小生成树的Kruskal算法,维护一个并查集,将图中边按照边权自小而大排序,依次取出边,如果这条边上两个顶点还分属不同集合,就合并这两个集合,直到图中n个点连通为止。
对于本题而言,所有边的边权都可以看作1,但是我们依旧可以优先选择第三类边,因为第三类边一条顶两条,比如a到b之间要想连通,可能需要先连一条第一类边,再连一条第二类边,这样两个人才都能从a走到b,但是如果优先连第三类边就划算了,只用连一条,符合连最少边的要求。如果要详细证明的话,断开该方案生成的最小生成树中的一条第三类边,根据最小生成树的性质,原本连通的连通块就不在连通,需要再连一条或者两条边才能恢复连通性,不会存在更优的替代方案使得连边更少。
对边的集合按照第三类边优先的顺序排序后,我们逐个取出边,如果这条边是第一类边,就判断在第一类边的集合里这两个点是否分属同一个集合,不是则合并;第二类边处理方法一样;第三类边就需要再第一类边和第二类边的集合里都判断下是否要合并集合了。也就说本题需要维护A和B两个人两个不同的并查集,除此之外没有难点。最后遍历下两个并查集,如果还存在不在一个集合内的两个点,说明没有合法方案,返回-1。
代码
class Solution {
public:
int fa1[100005],fa2[100005];
int find(int u,int *p){
if(u != p[u]) p[u] = find(p[u],p);
return p[u];
}
int maxNumEdgesToRemove(int n, vector<vector<int>>& edges) {
sort(edges.begin(),edges.end());
for(int i = 1;i <= n;i++) fa1[i] = fa2[i] = i;
int type,u,v;
int res = 0,m = edges.size();
for(int i = m-1;i >= 0;i--) {
type = edges[i][0],u = edges[i][1],v = edges[i][2];
int a1 = find(u,fa1),a2 = find(u,fa2);
int b1 = find(v,fa1),b2 = find(v,fa2);
if(type != 2) {
int a1 = find(u,fa1),b1 = find(v,fa1);
if(a1 != b1) {
fa1[a1] = b1;
res++;
}
}
if(type > 1) {
int a2 = find(u,fa2),b2 = find(v,fa2);
if(a2 != b2) {
fa2[a2] = b2;
if(type != 3) res++;
}
}
}
int p1 = find(1,fa1),p2 = find(1,fa2);
for(int i = 2;i <= n;i++){
int u = find(i,fa1),v = find(i,fa2);
if(u != p1 || v != p2) return -1;
}
return m - res;
}
};