Leetcode 1579. 保证图可完全遍历(思维 + 生成树)

Description
在这里插入图片描述
Solution
首先注意到本题中的“完全遍历”概念本质上就是某人能走的边集为原图的一颗生成树
故我们可以借鉴生成树算法的思想:避圈法(也就是kruskal的核心思想)

再做思考,可删除的边分为两类,一类是某人独有的边,一类是二人共有的
若我们删除某人独有的边,在不影响它本身的“完全遍历”性质的前提下,对另一个人的该性质无影响
所以我们若要删除尽可能多的边,就应该优先删除独有边,再删除共有边

本题一个不可忽视的条件

所有元组 (typei, ui, vi) 互不相同

该条件说明: 对于某个人来说的重边最多两条,一条是独有边,一条是共有边

对于某个人来说,按以下步骤构造其边集:

  1. 若出现重边,删除独有边,留下共有边
  2. 将所有的共有边加入边集
  3. 枚举步骤一之后剩下的独有边,若加入该边不会产生环,则加入,反之则删除

经过以上三步后,该人的边集中不存在重边(步骤1), 但可能存在环(步骤2), 若存在环,只可能是全为共有边的环(步骤3)(因为存在独有边的环被步骤3所排除了),且独有边被尽可能多的删除了
此时若有孤立点存在,则说明原图不满足条件,输出 -1

对两个人分别按照以上三步构造出两个边集后,边集中还能删去的边一定是环上的边(因为不存在重边),而边集中若存在环则只可能是全为共有边的环,所以还能删去的边一定是共有边,其数量就等于一个边集中环的个数(两个边集相等)

策略的正确性粗略证明完毕,考虑实现的便捷性

我们发现,其实共有边可以优先一律加入边集(不考虑避圈原则,满足步骤1的策略),再以避圈原则一条条加入独有边(同时满足步骤2和步骤3的策略),于是边集构造完毕,设此时每个人的边集大小为tot, 则每个人最多还能删去的边数为tot - (n-1),而两个人还能删去的边数也就为二者tot - (n-1)的最小值

最后再加上构造边集步骤中所删去的独有边,答案就得出了

Hint
避圈法以及孤立点的判断都可用并查集简单实现
但本题的重点不在并查集,并查集只是一种实现工具,核心是生成树的构造

Code

class Solution {
public:
    #define N 100005
    int fa[N][2];
    int find(int x,int op) {return x == fa[x][op] ? x : fa[x][op] = find(fa[x][op], op);}
    vector<int>t1,t2;
    void init(int n) {
        for(int i = 0;i <= n;++i) fa[i][0] = fa[i][1] = i;
    }
    int maxNumEdgesToRemove(int n, vector<vector<int>>& edges) {
        int cnt3 = 0, tot1 = 0, tot2 = 0;
        init(n);
        for(int i = 0;i < edges.size();++i) {
            int u = edges[i][1], v = edges[i][2], c = edges[i][0];
            if(c == 3) {
                int f1 = find(u,0), f2 = find(v,0);
                fa[f1][0] = f2;
                f1 = find(u,1), f2 = find(v,1);
                fa[f1][1] = f2;
                cnt3++;tot1++, tot2++;
            } else if(c == 1) {
                t1.push_back(i);
            } else if(c == 2) {
                t2.push_back(i);
            }
        }
        int res = 0;
        for(int i = 0;i < t1.size();++i) {
            int id = t1[i];
            int u = edges[id][1], v = edges[id][2];
            int f1 = find(u,0), f2 = find(v,0);
            if(f1 == f2) res++; 
            else {
                fa[f1][0] = f2;
                tot1++;
            }
        }

        for(int i = 0;i < t2.size();++i) {
            int id = t2[i];
            int u = edges[id][1], v = edges[id][2];
            int f1 = find(u,1), f2 = find(v,1);
            if(f1 == f2) res++;
            else {
                fa[f1][1] = f2;
                tot2++;
            } 
        }
        bool flag = true;
        int as1 = find(1,0), as2 = find(1,1);
        for(int i = 1;i <= n;++i) {
            int f1 = find(i,0), f2 = find(i,1);
            if(f1 != as1 || f2 != as2) {flag = false;break;}
        }
        if(!flag) return -1;
        res += min(tot1 - (n-1), tot2 - (n-1));
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值