蓝桥舞会 树形dp

也就是孙子可以和爷爷一起参加,当不可以个亲爹一起参加
一个儿子只有一个爹,但可以有多个儿子,因此采用邻接表来存储一个点的所有儿子
每个节点都有一个权值,求整棵树上最大的权值


对于除了叶子结点的每个点:
当一个点选了,它的直接儿子不能选,反之亦然
当这个点没选,则它的儿子可以选当然也可以不选,具体选不选,应比较选与不选哪种所获得的权值最大

因此,状态有两个维度:每个节点,该节点选与不选。(第二维中0表示不选这个节点,1表示选这个节点)
dp数组中存的应该是以这个点为根的整颗子树的累计最大权值
因此应该从叶子节点开始向上更新状态,直到根节点就是最终解
最终问题:max(dp[0][0],dp[0][1])表示根节点选与不选所获得的整棵树的最大权值
子问题:
dp[i][0]  表示第i个节点不选,其子树的累计最大权值
dp[i][1]  表示第i个节点选,其子树的累计最大权值

状态转移方程:
dp[i][0] = max(dp[j][0],dp[j][1])   只有当i点不选时,其子节点才可以选,也可以不选,由权值最大的决策转移过来
dp[i][1] = dp[j][0]       若i点选了,则其子节点只能不选,由子节点不选时的权值转移过来

首先需要先找到叶子结点,因此我们需要从根节点出发自上而下做dfs,
到达叶子结点后,再自下而上利用dp逐层递归更新状态,而根节点就是最终状态

因此我们需要
1.建图:构建一个邻接表存储每个节点所指向的点,用于找到他的子节点,
当一个点没有子节点时,它就是叶子结点  (子上而下寻找根节点时使用)
2.构建dp状态矩阵,用于记录新状态   (自底向上更新状态时使用)
3.因为根节点的编号不一定为0,比如样例中就是1,所以还要构建一个标记数组,
  标记所有有爹的节点,从而找到根节点
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+9;
long long a[N],dp[N][2];//a[]用来存储快乐指数
//dp[u][0]表示在以u为根节点的子树中选择两两不相邻的若干个点,不选点u的最大权值和
//dp[u][1]表示在以u为根节点的子树中选择两两不相邻若干个点,选了点u的最大权值和 

vector<int> e[N];//用vector来存储树的边
set<int> st;//用set来找根节点

void dfs(int v)
{
  for(auto u:e[v])//遍历v的所有子节点u
  {
    dfs(u);//从u开始遍历搜索整个树
    dp[v][1]=dp[v][1]+dp[u][0];//选了点v表明此时不能选子节点
    dp[v][0]=dp[v][0]+max(dp[u][0],dp[u][1]);//不选点v表明此时可以选其中一个子节点或不选的情况下得到的最大值
  }
  dp[v][1]+=a[v];//初始化根节点的权值
}

int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++)
  {
    cin>>a[i];//输入每个人的欢乐指数
    st.insert(i);//每个人一开始都可能是根节点,将每个人都存入到set中去
  }
  for(int i=1;i<n;i++)
  {
    int u,v;
    cin>>u>>v;
    e[v].push_back(u);//v是u的直接上司,所以在e[v]中添加u表明u是v的子节点
    st.erase(u);//u上面有v说明u不是董事长,所以把u从set中删除(set中存储可能是根节点的人)
  }
  int root=*st.begin();//经过确定直接上司后对set删除的操作最终找到根节点(即董事长)
  dfs(root);//从根节点开始搜索
  cout<<max(dp[root][0],dp[root][1])<<endl;//找出从根节点开始分别选根节点与不选根节点的搜索情况下的最大值
  return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+9;
int a[N],dp[N][2],fa[N];
//dp[i][0]表示不选择i时的快乐指数最大值,dp[i][1]表示选择i时的快乐指数最大值
//a[N]存储快乐指数,fa[N]存储是否有父节点
vector<int> graph[N];//用于存储tree

void dfs(int u)
{
  dp[u][1]=a[u];//选u时,初始的快乐指数
  for(int i=0;i<graph[u].size();i++)//遍历以u为根节点的子节点
  {
    dfs(graph[u][i]);//dfs子节点
    dp[u][1]+=dp[graph[u][i]][0];//选u节点,则不能选子节点
    dp[u][0]+=max(dp[graph[u][i]][0],dp[graph[u][i]][1]);//不选u节点,则子节点可选可不选,取最大值
  }
}

int main()
{
  int n;cin>>n;
  for(int i=1;i<=n;i++) cin>>a[i];
  for(int i=1;i<n;i++)
  {
    int u,v;cin>>u>>v;
    graph[v].push_back(u);
    fa[u]=1;
  }
  //寻找整棵树的根节点
  int root=1;
  while(fa[root]) root++;
  //从root开始dfs
  dfs(root);
  //输出结果为 选root 和 不选root 中的最大值
  cout<<max(dp[root][0],dp[root][1])<<endl;
  return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值