LeetCode每日一题【c++版】- leetcode 2581. 统计可能的树根数目【换根dp】

文章介绍了如何利用深度优先搜索算法解决一个题目,给定一个有向图和一些猜测的边关系,计算以不同节点为树根时,至少有k条猜测正确的组合数。主要涉及树形动态规划和状态转移的递归实现。
摘要由CSDN通过智能技术生成

题目描述

题目链接:2581. 统计可能的树根数目

输入输出描述

示例1:

输入:edges = [[0,1],[1,2],[1,3],[4,2]], guesses = [[1,3],[0,1],[1,0],[2,4]], k = 3
输出:3
解释:
根为节点 0 ,正确的猜测为 [1,3], [0,1], [2,4]
根为节点 1 ,正确的猜测为 [1,3], [1,0], [2,4]
根为节点 2 ,正确的猜测为 [1,3], [1,0], [2,4]
根为节点 3 ,正确的猜测为 [1,0], [2,4]
根为节点 4 ,正确的猜测为 [1,3], [1,0]
节点 0 ,1 或 2 为根时,可以得到 3 个正确的猜测。

示例2:

输入:edges = [[0,1],[1,2],[2,3],[3,4]], guesses = [[1,0],[3,4],[2,1],[3,2]], k = 1
输出:5
解释:
根为节点 0 ,正确的猜测为 [3,4]
根为节点 1 ,正确的猜测为 [1,0], [3,4]
根为节点 2 ,正确的猜测为 [1,0], [2,1], [3,4]
根为节点 3 ,正确的猜测为 [1,0], [2,1], [3,2], [3,4]
根为节点 4 ,正确的猜测为 [1,0], [2,1], [3,2]
任何节点为根,都至少有 1 个正确的猜测。

解题思路

        通常来说这种题目的套路就是先随便定一个根,比如0作为整棵树的根节点,然后从0开始进行DFS,计算出其他所有子树i中,属于guesses中的边有多少条,记录在f[i]中,这一步很简单,就是个简单的树形DP。

        然后再进行一次DFS进行状态转移,关键就在于这个状态转移的递归函数怎么实现。dfs(u,fa,top)表示考虑以u作为整棵树的根,top表示把根调整为u之前(即以u的父节点fa为根时)属于u上面(祖先方向)的子图中有多少条猜测中的边。此时,如果递归到u的某个孩子v时,我们就要计算v的top是多少;


    gain=f[u]−f[v]就是v的其他兄弟节点对v节点top值的额外贡献,因为其他兄弟节点在v换成根之后会成为它的子树。
    如果u->v(箭头表示父节点指向子节点)这条边在guesses中,就还需要在gain中减去这条边。但如果v->u这条边在guesses中,gain就要加上这条边,此时我们得到v的top值更新为top=top+gain


        每当遍历到一个节点u,只要满足f[u]+top>=k,说明这个节点作为整棵树的根时可以保证guesses中至少有k条正确,答案ans自增即可。第二次DFS遍历完成后,返回ans的值即可。两次dfs的说明如下:

  1. 第一次找到以某个节点(比如 0)作为根节点时的正确数 zero_root_correct,作为状态转移的初始值。
  2. 第二次我们通过遍历到父子节点,来对正确数进行转移,从而得到每个节点作为根节点时的正确数 correct。

复杂度

时间复杂度:O(n+m),n表示点数,m表示guesses的长度。

空间复杂度:O(n+m),n表示点数,m表示guesses的长度。

代码

#include <iostream>
#include <vector>
#include <unordered_set>
#include <set>
using namespace std;
class Solution {
public:
    int rootCount(vector<vector<int>> &edges, vector<vector<int>> &guesses, int k)
    {
        int n = edges.size() + 1;  // n个节点
        // 获取每个节点的邻居节点
        vector<vector<int>> neighbors(n);
        for (auto &e : edges) {
            int x = e[0], y = e[1];
            neighbors[x].emplace_back(y);
            neighbors[y].emplace_back(x);
        }
        // 生成猜测数对的id = u * K + v
        unordered_set<long long> guessesIds;
        for (auto &g : guesses) {
            int u = g[0], v = g[1];
            guessesIds.emplace(u * K + v);
        }
        // 获取以0节点为根时的正确数
        int zeroRootCorrect = dfs_GetZeroRootCorrect(0, -1, neighbors, guessesIds);
        // 以0节点为根,其正确数为起始值,再一次遍历所有节点,并得到其他节点作为根节点的正确数
        dfs_ChangeRoot(0, -1, k, zeroRootCorrect, neighbors, guessesIds);
        return res;
    }
private:
    const long long K = 10000;  // 偏移系数
    int res = 0;                // 结果值

    /**
     * 获取以0节点为根时的正确数
     */
    int dfs_GetZeroRootCorrect(int node, int fa, vector<vector<int>> &neighbors, unordered_set<long long> &guessesIds)
    {
        int correct = 0;  // 统计正确数
        for (auto &child : neighbors[node]) {
            if (child != fa) {
                correct += (guessesIds.count(node * K + child));  // 当前父子节点对(node,child)存在于guessIds中,猜中
                correct += dfs_GetZeroRootCorrect(child, node, neighbors, guessesIds);  // 递归搜索子树情况
            }
        }
        return correct;
    }

    /**
     * 通过换根策略,得到每个节点为根时的正确数
     */
    void dfs_ChangeRoot(
        int node, int fa, int k, int correct, vector<vector<int>> &neighbors, unordered_set<long long> &guessesIds)
    {
        if (correct >= k)
            res++;  // 以node为根节点的猜中数correct大于等于k,是一种可能的情况
        for (auto &child : neighbors[node]) {
            if (child != fa) {
                // 递归搜索子节点
                // 如果当前(node,child)存在,当前是猜中的,下一步递归到子节点,子节点变成了根节点,就是猜错了,因此正确数要-1
                // 如果(child,node)存在,下一步递归到子节点,子节点变成了根节点,原本父节点就变成子节点,就是猜对了,因此正确数要+1
                dfs_ChangeRoot(child,
                    node,
                    k,
                    correct - guessesIds.count(node * K + child) + guessesIds.count(child * K + node),
                    neighbors,
                    guessesIds);
            }
        }
    }

};
int main()
{
    // edges = [[0,1],[1,2],[1,3],[4,2]], guesses = [[1,3],[0,1],[1,0],[2,4]], k = 3 输出:3
    vector<vector<int>> edges0 = {{0, 1}, {1, 2}, {1, 3}, {4, 2}};
    vector<vector<int>> guesses0 = {{1, 3}, {0, 1}, {1, 0}, {2, 4}};
    int k0 = 3;
    int res0 = Solution().rootCount(edges0, guesses0, k0);
    cout << "res0: " << res0 << endl;

    // edges = [[0,1],[1,2],[2,3],[3,4]], guesses = [[1,0],[3,4],[2,1],[3,2]], k = 1 输出:5
    vector<vector<int>> edges1 = {{0, 1}, {1, 2}, {2, 3}, {3, 4}};
    vector<vector<int>> guesses1 = {{1, 0}, {3, 4}, {2, 1}, {3, 2}};
    int k1 = 1;
    int res1 = Solution().rootCount(edges1, guesses1, k1);
    cout << "res1: " << res1 << endl;
}

  • 20
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值