题意分析

  • 给你一棵树,需要我们将这棵树划分为k个联通块,同时每个联通块的节点的权值的和都要大于等于给定的值,问是否可以达到目的。

思路分析

  • 我们观察得到,为了确保是联通块,而且达到贪心的目的,我们应该从树的叶子节点往上面进行扩展。如果某一个节点和他的子树可以构成一个满足题目条件的联通块。那么我们就将这棵树和他的结点的权值都置为0.继续向上进行扩展。这是一个树形DP的思想。当然,也可以用BFS进行处理。

  • 如下图

图片说明

解法一 DFS(DP)

  • 根据上面的分析,我们可以很好的写出这个DP函数,首先,从根节点开始进入,然后,处理字节点,同时判断此时的子节点是否可以构成一个满足条件的联通块。可以就将权值都置为0.

  • 代码如下

    • 需要遍历所有的结点以及他们的连边。所以,时间复杂度为
    • 需要存储所有的边的情况以及权值情况。所以空间复杂度为
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 
     * @param k int整型 
     * @param m int整型 
     * @param u int整型vector 
     * @param v int整型vector 
     * @param x int整型vector 
     * @return bool布尔型
     */
    vector<int> node[200010];
    int sum[200010];
    int res=0;
    // 树形DP
    void dp(int x,int fa,int m,vector<int>& tmp){
        sum[x]=tmp[x-1];
        for(auto y:node[x]){

            // 遍历所有的连边的结点
            if(y==fa) continue;
            dp(y,x,m,tmp);
            sum[x]+=sum[y];
        }

        // 如果最后这个结点和它的子树构成的金币的和大于等于m,那么就将这一块进行合并
        if(sum[x]>=m){
            sum[x]=0;
            res++;
        }
    }
    bool solve(int n, int k, int m, vector<int>& u, vector<int>& v, vector<int>& x) {
        // write code here
        // 存储这颗树
        for(int i=0;i<n-1;i++){
            node[v[i]].push_back(u[i]);
            node[u[i]].push_back(v[i]);
        }

        dp(1,0,m,x);
        return res>=k;
    }
};

解法二 BFS

  • 我们上面知道,我们需要从叶子节点开始向上进行扩展。那么我们如何知道叶子节点在什么位置呢?有一个好的方法是计算每个节点的入度,入度为1的就说明是叶子节点了。然后,我们将这些节点放到队列里面,不断出队往根节点的方向进行扩展。同时维护满足条件的联通块的个数。

  • 代码如下

    • 队列可能会存储n个节点的情况,需要处理n个节点的情况,时间复杂度为.
    • 队列存储了n个节点的情况,而且会使用到一些数组维护权值的情况,空间复杂度为
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 
     * @param k int整型 
     * @param m int整型 
     * @param u int整型vector 
     * @param v int整型vector 
     * @param x int整型vector 
     * @return bool布尔型
     */
    vector<int> node[200010];
    int sum[200010],in[200010];
    bool vis[200010]={false};
    bool solve(int n, int k, int m, vector<int>& u, vector<int>& v, vector<int>& x) {
        // write code here
        // 存储这颗树
        for(int i=0;i<n-1;i++){
            node[v[i]].push_back(u[i]);
            in[v[i]]++;
            node[u[i]].push_back(v[i]);
            in[u[i]]++;
        }
        queue<int> q;

        // 先把叶子结点放到放进队列里面,从树的底部一层一层扩展到根结点
        for(int i=1;i<=n;i++){
            sum[i]=x[i-1];
            if(in[i]==1){
                q.push(i);
                vis[i]=true;
            }
        }
        int res=0;
        while(!q.empty()){
            int now=q.front();
            q.pop();

            // 如果满足题目的条件,那么就执行合并的操作
            if(sum[now]>=m){
                res++;
                sum[now]=0;
            }
            for(auto y:node[now]){
                // 父节点合并子节点的权值
                sum[y]+=sum[now];


                // 如果这个结点没有被合并过
                if(!vis[y]){
                    vis[y]=true;
                    q.push(y);
                }
            }
        }
        return res>=k;
    }
};