zoukankan      html  css  js  c++  java
  • 【bzoj4027】[HEOI2015]兔子与樱花 树形dp+贪心

    题目描述

    很久很久之前,森林里住着一群兔子。有一天,兔子们突然决定要去看樱花。兔子们所在森林里的樱花树很特殊。樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1个树枝连接,我们可以把它看成一个有根树结构,其中0号节点是根节点。这个树的每个节点上都会有一些樱花,其中第i个节点有c_i朵樱花。樱花树的每一个节点都有最大的载重m,对于每一个节点i,它的儿子节点的个数和i节点上樱花个数之和不能超过m,即son(i) + c_i <= m,其中son(i)表示i的儿子的个数,如果i为叶子节点,则son(i) = 0

    现在兔子们觉得樱花树上节点太多,希望去掉一些节点。当一个节点被去掉之后,这个节点上的樱花和它的儿子节点都被连到删掉节点的父节点上。如果父节点也被删除,那么就会继续向上连接,直到第一个没有被删除的节点为止。
    现在兔子们希望计算在不违背最大载重的情况下,最多能删除多少节点。
    注意根节点不能被删除,被删除的节点不被计入载重。

    输入

    第一行输入两个正整数,n和m分别表示节点个数和最大载重

    第二行n个整数c_i,表示第i个节点上的樱花个数

    接下来n行,每行第一个数k_i表示这个节点的儿子个数,接下来k_i个整数表示这个节点儿子的编号

    输出

    一行一个整数,表示最多能删除多少节点。

    样例输入

    10 4
    0 2 2 2 4 1 0 4 1 1
    3 6 2 3
    1 9
    1 8
    1 1
    0
    0
    2 7 4
    0
    1 5
    0

    样例输出

    4

    提示

    对于100%的数据,1 <= n <= 2000000, 1 <= m <= 100000, 0 <= c_i <= 1000

    数据保证初始时,每个节点樱花数与儿子节点个数之和大于0且不超过m


    题解

    树形dp+贪心

    每次删除选定节点后,增加的重量为(选定节点的子节点数目+选定节点的樱花数)-1,

    那么我们完全可以讲每个节点的重量看作子节点数目+樱花数。

    于是就有贪心策略:优先选择重量小的子节点删除,否则若选择其它子节点,那么这个节点删掉的子节点不会增多,而且这个节点的重量会比贪心方案大,影响后面的处理。

    先更新子节点的重量,并从小到大排序,根据贪心策略优先选择重量小的,判断能否去掉,若能去掉则更新当前节点的重量。

    时间复杂度为O(nlogn),但常数较小,可以过。

    #include <stdio.h>
    #include <algorithm>
    #include <vector>
    using namespace std;
    vector<int> son[2000001];
    int c[2000001] , m , ans;
    bool cmp(int a , int b)
    {
        return c[a] < c[b];
    }
    void dp(int x)
    {
        int i;
        for(i = 0 ; i < (int)son[x].size() ; i ++ )
            dp(son[x][i]);
        sort(son[x].begin() , son[x].end() , cmp);
        c[x] += son[x].size();
        for(i = 0 ; i < (int)son[x].size() ; i ++ )
        {
            if(c[x] + c[son[x][i]] - 1 <= m)
            {
                c[x] += c[son[x][i]] - 1;
                ans ++ ;
            }
            else
                break;
        }
    }
    int main()
    {
        int n , i , k , x;
        scanf("%d%d" , &n , &m);
        for(i = 1 ; i <= n ; i ++ )
            scanf("%d" , &c[i]);
        for(i = 1 ; i <= n ; i ++ )
        {
            scanf("%d" , &k);
            while(k -- )
            {
                scanf("%d" , &x);
                son[i].push_back(x + 1);
            }
        }
        dp(1);
        printf("%d
    " , ans);
        return 0;
    }
  • 相关阅读:
    打造基于CentOS7的xfce最简工作环境
    Linux下C程序的编辑,编译和运行以及调试
    修正 XE6 TListView 上方 SearchBok 右边的清除钮显示
    TabControl 显示彩色的图示 (XE6 Firemonkey)
    TSwitch 中文简繁显示支持(XE6 Android)
    改变 TMemo 的背景颜色 (Firemonkey)
    修正 XE5 Android 键盘三个问题
    Delphi 收藏
    展示 Popup 的使用方法
    ListView 下拉更新 (支持 Android)
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/6164080.html
Copyright © 2011-2022 走看看