树状DP(1)——美团2021.03.13笔试_蚂蚁上树

本文介绍了如何使用树形动态规划解决蚂蚁上树的问题,即在树形结构中选择节点使得蚂蚁队伍数量最大化且最小队伍数量最大化。通过定义状态dp和mi数组,分别记录以某个节点为根的最大权值和最小值,再进行深度优先搜索更新状态。最后,从根节点开始计算,得出最大权值和最大化的最小值。代码示例展示了具体的实现过程。

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

题目描述

要求:

蚂蚁们爬树做游戏。
同一个队伍的蚂蚁站在树上的同一个节点;不同队伍的蚂蚁间隔至少一个空节点。
树上每个节点的容纳蚂蚁数量有限。

目的:

蚂蚁数最多的情况下,蚂蚁数最少的队伍蚂蚁数最多。

程序描述:

给定一棵树,每个节点有一个权值。
选择其中节点,满足被选中的节点两两不相邻。
使得被选择节点权值最大的情况下,最小权值尽可能大。

输入样例:

5 4
3 4 1 4 9
1 2
1 3
2 4
3 5

输出样例:

16 3

样例解释:

当选择1,4,5号节点时,有最大权值之和16,最小权值为1号节点的3,此时为最优解。

输入描述:

第一行数n,m表示树上节点数和边数。
第二行n个数a1,…, an。ai表示第i个节点上可以容纳的最大蚂蚁数。
接下来m行,每行两个数u,v,表示u和v节点可以直接相连。
无重复边无自环,数据为整数。

输出描述:

输出一行,包含两个数,分别表示最大权值和以及最大化的最小值。

参考


分析

一.状态

从dp的角度考虑,先把状态确定好;
而且题目要求输出最大权值和最大化的最小值两个东西,那就用两个数组来保存:
dp[N][2],值表示权值和
mi[N][2],值表示最小值

对于每个节点,有选或者不选两个状态
dp[u][0],选了当前节点的权值和
dp[u][1],不选当前节点的权值和
mi[u][0],选了当前节点的最小值
mi[u][1],不选当前节点的最小值

二.状态转移

dp无非就是把大问题转移成小问题,这道题的状态转移也比较容易想到。

1 选了当前节点,则子节点一定不能选

dp[u][1]+=dp[v][0], 其中v是u的子节点

那么最小值就用 选了当前节点后的最小值不选子节点后的最小值 比较

mi[u][1]=min( mi[u][1], mi[v][0] )

2 不选当前节点,则子节点可选可不选
那子节点到底选不选呢,优先考虑的应该是权值和最大,所以哪种情况大就选哪种情况

dp[u][0]+=max( dp[v][0], dp[v][1])

最大权值和就这么搞定了。
问题来到了最小值上,这个时候就必须分清楚到底子节点有没有选了。

2.1 选了子节点 ( dp[v][1] > dp[v][0] )
既然选了,那就好说,比较两个值就行

mi[u][0]=min( mi[u][0], mi[v][1] )

2.2 没选子节点 ( dp[v][1] < dp[v][0] )
既然没选,那也好说,还是比较两个值

mi[u][0]=min( mi[u][0], mi[v][0] )

2.3 选不选一样 ( dp[v][1] = dp[v][0] )
这就头疼了。
但是题目中第二个条件:最大的最小值,就可以利用到。
我们只需要看到底哪个的最小值比较大,就行了。

mi[u][0]=min( mi[u][0], max( mi[v][0], mi[v][1] ) )

以上,转移过程就解决了,只需要从根部开始dfs,然后就得到了每个点的最大权值和,以及对应的最大的最小值。


代码实现

参考的这个,第4题,链接

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+50;
ll val[N];
vector<int> e[N];
ll dp[N][2];//表示以i为根的子树选了  1/0  当前根/不选根 的最大值
ll mi[N][2];
void dfs(int x,int fa){
    dp[x][1]=val[x];
    mi[x][1]=val[x];
    dp[x][0]=0;
    for(auto &son:e[x]){
        if(son==fa) continue;
        dfs(son,x);
        dp[x][1]+=dp[son][0];
        mi[x][1]=min(mi[x][1],mi[son][0]);
 
        dp[x][0]+=max(dp[son][1],dp[son][0]);
        if(dp[son][1]>dp[son][0]) mi[x][0]=min(mi[x][0],mi[son][1]);
        else if(dp[son][1]<dp[son][0]) mi[x][0]=min(mi[x][0],mi[son][0]);
        else mi[x][0]=min(mi[x][0],max(mi[son][0],mi[son][1]));
 
    }
}
int main(){
    memset(mi,63,sizeof mi);
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>val[i];
    }
    for(int i=1;i<n;i++){
        int x,y;cin>>x>>y;
        e[x].push_back(y);
        e[y].push_back(x);
    }   
    dfs(1,0);
    ll S1=0,S2=0;
    for(int i=1;i<=n;i++){
        S1=max(S1,max(dp[i][0],dp[i][1]));
    }
    cout<<S1<<" ";
    for(int i=1;i<=n;i++){
        if(S1==dp[i][0]) S2=max(S2,mi[i][0]);
        if(S1==dp[i][1]) S2=max(S2,mi[i][1]);
    }
    cout<<S2;
 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值