2021 南京 H (树上DP)

题意:

给一个树,并且每个节点都有一只蝴蝶,蝴蝶具有价值val。起初,你在1节点,你可以选择到下一个点去拿蝴蝶,获得它的价值,拿蝴蝶的时间不计。每个蝴蝶都有一个停留时间,当你在树上的某个节点时,该节点下的所有孩子结点处的蝴蝶的停留时间开始计时,当到达停留时间时,蝴蝶会飞走,该地方变成空的,此外,蝴蝶的停留时间满足 1 <= t <= 3,树的节点的数量1 <= n <= 1e6,从相连的一点走到另一点花费时间为1,可假设总共供捡蝴蝶的时间为无穷大;

输入:

给一个数n,代表节点的数量,接下来是n个值,代表每个蝴蝶的价值val,然后下一行的n个值代表的是每个节点处蝴蝶的停留时间t,接下来给n - 1条边,代表x与y之间有一条边。

输出:

输出最大可能捡到的val值总和。

思路:

首先,显然是个树上dp,对于绝大部分树上DP问题,首先要讨论选择儿子或者不选择儿子,以便对儿子的影响能够通过两个状态来算出来。

该题,f [ u ]为u节点能够获得的最大价值val,g [ u ] 节点为不选择 u 的所有孩子可以获得的最大价值val.

对于 g [ u ] 的计算比较简单,由于不选的子节点,所以对与孙子节点的选择没有影响,所以可得

                                g [ u ] = sum ( f [ v ] - val [ v ] ); 

接下来,对于 f [ u ] 的计算,首先要考虑,是否有些一层可以拿多个孩子,此时便可以考虑到  

t [ v] = 3时,可以拿了兄弟再回来拿。

因此,接下来分情况考虑 :

对于t <= 2的蝴蝶,只要到了父亲节点,所有的兄弟节点只可以选择一个。

                                 f [ u ] = max ( f [ u ], g[ u ] + val [ v ] );

对于 t == 3 的蝴蝶 v ,由于可以选了一个兄弟 br 之后再回来选择,所以此时

该兄弟下 br 的儿子不可以选择,该兄弟处的贡献最大值变为了 g [ br ],而自己处可以选择,可以得到选择某兄弟 br 可得最大贡献:

                       f [ u ] = max ( f [ u ], g [ u ] + val [ v ] + g [ br ] - ( f [ br ] - val [ br ] );

对于相减的原因是,首先g [ u ] 会带上 br 的孩子,而加上 br 后,也会带上 br 的孩子,所以 br 的孩子加了两次,为了减去这两次,可以考虑到br 处取得最大值,而不选上 br 的值,便是 

                                                        f [ br ] - val [ br ];

我们可以发先,在遍历 u 的儿子时,记录 g [ v ] - f [ v ] + val [ v ] 的值,便可以 on 记录下最优选择的兄弟。

坑点:

memset会超时,不能memset;

cin其实也会超时(IOS 不会);

记录最优的 br 时,还应该考虑,这个 br 是不是自己,所以要记录最大值和次大值(相同没事);

代码如下:

// f , g , t 的含义如上

// G记录边

// p 就是val

#include <bits/stdc++.h>
#define pb push_back
#define int long long
using namespace std;

const int maxn = 1e6 +5;
const int inf = 0x3f3f3f3f;

int f[maxn],g[maxn];
vector <int> G[maxn];
int t[maxn],p[maxn],n;

void DP(int fa,int u)
{
//    cout << x << endl;
    g[u] += p[u];
    int maxx = -inf,maxy = -inf;
    for(auto v:G[u]) {
        if(v == fa) continue;
        DP(u,v);
        g[u] += f[v] - p[v];
//        cout << g[v] << " " << f[v] << " " << v << endl;
        int now = g[v] - f[v] + p[v];
        if(now > maxx) maxy = maxx,maxx = now;
        else if(now > maxy) maxy = now;
    }
    f[u] = g[u];
    for(auto v:G[u]) {
        if(v == fa) continue;
        f[u] = max(f[u],g[u] + p[v]);
        if(t[v] == 3) {
            if(g[v] - f[v] + p[v] == maxx) f[u] = max(g[u] + p[v] + maxy,f[u]);
            else f[u] = max(g[u] + p[v] + maxx,f[u]);
        }
    }
}

signed main()
{
    ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T;cin >> T;
    while(T--) {
        cin >> n;
        for(int i = 1;i <= n;i++) cin >> p[i],G[i].clear(),f[i] = g[i] = 0;
        for(int i = 1;i <= n;i++) cin >> t[i];
        for(int i = 1;i < n;i++) {
            int x,y;cin >> x >> y;
            G[x].pb(y);
            G[y].pb(x);
        }
        DP(-1,1);
//        for(int i = 1;i <= n;i++) {
//            cout << f[i] << " ";
//        }
//        cout << endl;
        cout << f[1] << endl;
    }
    return 0;
}
/*
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值