LeetCode 第299次周赛 第4题 从树中删除边的最小分数

该博客探讨了一种利用树形动态规划(DP)和最近公共祖先(LCA)算法解决的图论问题。在给定的无向连通树中,寻找删除两条边以形成三个连通组件,使得最大异或值与最小异或值之差最小。博主通过详细解释和AC代码展示了如何计算这个最小分数,涉及的关键概念包括树的DFS遍历、异或操作和LCA查找。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

存在一棵无向连通树,树中有编号从 0 到 n - 1 的 n 个节点, 以及 n - 1 条边。

给你一个下标从 0 开始的整数数组 nums ,长度为 n ,其中 nums[i] 表示第 i 个节点的值。另给你一个二维整数数组 edges ,长度为 n - 1 ,其中 edges[i] = [ai, bi] 表示树中存在一条位于节点 ai 和 bi 之间的边。

删除树中两条 不同 的边以形成三个连通组件。对于一种删除边方案,定义如下步骤以计算其分数:

  1. 分别获取三个组件 每个 组件中所有节点值的异或值。
  2. 最大 异或值和 最小 异或值的 差值 就是这一种删除边方案的分数。
  • 例如,三个组件的节点值分别是:[4,5,7][1,9] 和 [3,3,3] 。三个异或值分别是 4 ^ 5 ^ 7 = 61 ^ 9 = 8 和 3 ^ 3 ^ 3 = 3 。最大异或值是 8 ,最小异或值是 3 ,分数是 8 - 3 = 5 。

返回在给定树上执行任意删除边方案可能的 最小 分数。

示例 1:

输入:nums = [1,5,5,4,11], edges = [[0,1],[1,2],[1,3],[3,4]]
输出:9
解释:上图展示了一种删除边方案。
- 第 1 个组件的节点是 [1,3,4] ,值是 [5,4,11] 。异或值是 5 ^ 4 ^ 11 = 10 。
- 第 2 个组件的节点是 [0] ,值是 [1] 。异或值是 1 = 1 。
- 第 3 个组件的节点是 [2] ,值是 [5] 。异或值是 5 = 5 。
分数是最大异或值和最小异或值的差值,10 - 1 = 9 。
可以证明不存在分数比 9 小的删除边方案。

示例 2:

输入:nums = [5,5,2,4,4,2], edges = [[0,1],[1,2],[5,2],[4,3],[1,3]]
输出:0
解释:上图展示了一种删除边方案。
- 第 1 个组件的节点是 [3,4] ,值是 [4,4] 。异或值是 4 ^ 4 = 0 。
- 第 2 个组件的节点是 [1,0] ,值是 [5,5] 。异或值是 5 ^ 5 = 0 。
- 第 3 个组件的节点是 [2,5] ,值是 [2,2] 。异或值是 2 ^ 2 = 0 。
分数是最大异或值和最小异或值的差值,0 - 0 = 0 。
无法获得比 0 更小的分数 0 。

提示:

  • n == nums.length
  • 3 <= n <= 1000
  • 1 <= nums[i] <= 108
  • edges.length == n - 1
  • edges[i].length == 2
  • 0 <= ai, bi < n
  • ai != bi
  • edges 表示一棵有效的树

思路:

树形dp+倍增LCA

设xor[u]是以u为根节点的子树的异或和,我们跑一遍树形dp(dfs形式即可)即可统计;

由于数据量只有<=1000,我们再枚举删除那两条边(1000*1000),求出从总树中删除那两个子树,这两个子树可能存在包含关系,可能不相互包含;

1)不相互包含:

aa=xor[u]为以u为跟的子树的异或和,bb=xor[v]为以v为根节点的子树的异或和,则第三个连通块异或和为:cc=xor[1]^xor[u]^xor[v];

所以三个连通块的异或和分别为aa,bb,cc;

当前方案分数:max(aa,bb,cc)-min(aa,bb,vv);

2)相互包含:

假设以u为跟的子树包含以v为根的子树;

则以v为根的子树异或和:aa=xor[v];

以u为根的子树除去以v为根的子树剩余结点所形成的连通块异或和:bb=xor[u]^xor[v];

以1为根的子树(总树)除去以u为根的子树剩余结点所形成的连通块异或和:cc=xor[1]^xor[u];

所以三个连通块的异或和分别为aa,bb,cc;

当前方案分数:max(aa,bb,cc)-min(aa,bb,vv);

那么如何判断u,v子树相互包含呢?

使用LCA:

如果lca(u,v)==u,说明u包含v;

如果lca(u,v)==v,说明v包含u;

否则u,v互不包含;

时间复杂度:O(20*n^2);

AC Code:

struct Edge{
    int u,v,next;
};

class Solution {
public:
    int fa[1001];
    int head[1001];
    Edge e[2002];
    int val[1001];
    int cnt;
    int n,m;
    int xxor[1001];
    int dep[1001];
    int bz[1001][11];

    void add_edge(int u,int v){
        e[++cnt].next=head[u];
        head[u]=cnt;
        e[cnt].u=u;
        e[cnt].v=v;
    }

    void dfs(int u,int ff){
        xxor[u]=val[u];
        for(int i=head[u];i!=0;i=e[i].next){
            int v=e[i].v;
            if(v==ff)continue;
            fa[v]=u;
            dep[v]=dep[u]+1;
            dfs(v,u);
            xxor[u]^=xxor[v];
        }
    }

    int lca(int u,int v){
        if(dep[u]<dep[v])swap(u,v);
        for(int i=10;i>=0;--i){
            if(dep[bz[u][i]]>=dep[v]){
                u=bz[u][i];
            }
        }
        if(u==v)return u;
        for(int i=10;i>=0;--i){
            if(bz[u][i]!=bz[v][i]){
                u=bz[u][i];
                v=bz[v][i];
            }
        }
        return fa[u];
    }

    int minimumScore(vector<int>& nums, vector<vector<int>>& edges) {
        cnt=0;
        n=nums.size();
        m=edges.size();
        for(int i=1;i<=n;++i){
            val[i]=nums[i-1];
        }

        for(int i=0;i<m;++i){
            int u=1+edges[i][0];
            int v=1+edges[i][1];
            add_edge(u,v);
            add_edge(v,u);
        }
        
        fa[1]=0;
        dep[1]=1;
        dfs(1,0);
        for(int i=1;i<=n;++i){
            bz[i][0]=fa[i];
        }

        for(int i=1;i<=10;++i){
            for(int j=1;j<=n;++j){
                bz[j][i]=bz[bz[j][i-1]][i-1];
            }
        }
        
        int res=100000000;
        int aa,bb,cc;
        for(int i=0;i<m;++i){
            for(int j=i+1;j<m;++j){
                int son1=1+edges[i][1];
                int fa1=1+edges[i][0];
                if(dep[son1]<dep[fa1])swap(fa1,son1);

                int son2=1+edges[j][1];
                int fa2=1+edges[j][0];
                if(dep[son2]<dep[fa2])swap(fa2,son2);
                
                int lca_ab=lca(son1,son2);

                if(lca_ab==son1){
                    bb=xxor[son2];
                    aa=xxor[son1]^bb;
                    cc=xxor[1]^xxor[son1];
                }else if(lca_ab==son2){
                    aa=xxor[son1];
                    bb=xxor[son2]^aa;
                    cc=xxor[1]^xxor[son2];
                }else{
                    aa=xxor[son1];
                    bb=xxor[son2];
                    cc=xxor[1]^aa^bb;
                }

                int mx=max(aa,bb);
                int mi=min(aa,bb);
                if(cc>mx)mx=cc;
                if(cc<mi)mi=cc;
                res=min(res,mx-mi);

                //printf("i:%d j:%d son1:%d xorson1:%d son2:%d xorson2:%d xorroot:%d mx:%d mi:%d nowres:%d\n",i,j,son1,aa,son2,bb,cc,mx,mi,mx-mi);
            }
        }
        return res;
    }
};
/*
[29,29,23,32,17]
[[3,1],[2,3],[4,1],[0,4]]
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值