树状dp(含链式前向星)

本文介绍了树形动态规划的基本步骤,并通过三个具体题目——没有上司的舞会、二叉苹果树和树的最小点覆盖——进行详细解析,展示如何运用树形dp和链式前向星解决实际问题。每个题目都包含题解思路、代码实现和题目分析,帮助读者理解树状dp和链式前向星的应用。

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

首先先推荐dsy的《树形动规》

基本步骤:

在这里插入图片描述
在这里插入图片描述
第一步确定状态
用f[i][0]表示不选择i点时,i点及其子树能选出的最多的人数,
f[i][1]表示选择i点时,i点及其子树的最多人数
第二步:确定状态转移方程
f[i][0]= ∑(max(fa[j][0],f[j][1])) // j为i的儿子,以下都是
f[i][1] = 1+∑f[j][0]
边界:f[i][0]=0,f[i][1]=1 - - -i是叶节点
结果为max(f[root][0],f[root][1])
第三步:确定编程方式

void dfs(long long x)
{
   
   
    v[x]=1;
    for(long long i=1;i<=n;i++)
    {
   
   
        if(!v[i]&&(fa[i]==x))
        {
   
   
            dfs(i);
            f[x][0]+=max(f[i][1],f[i][0]);
            f[x][1]+=f[i][0];
        }
    }
}

P1352 没有上司的舞会

题目描述
某大学有N个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

输入输出格式
输入格式:
第一行一个整数N。(1<=N<=6000)

接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)

接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。

最后一行输入0 0

输出格式:
输出最大的快乐指数。

输入输出样例
输入样例#1:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
输出样例#1:
5


分析:与基本步骤相似
代码:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
const int N = 6010;

int n;
int fa[N];
int vis[N];
long long dp[N][5];//dp[i][0]表示不选i时,i点及其子树能选出的最大值,dp[i][1]表示选i的时,i点及其子树能选出的最大值

void dfs(int x)
{
   
   
    vis[x]=1;
    int i;
    for(i=1;i<=n;i++)
    {
   
   
        if(!vis[i]&&(fa[i]==x))
        {
   
   
            dfs(i);
            dp[x][0]+=max(dp[i][0],dp[i][1]);
            dp[x][1]+=dp[i][0];
        }

    }


}

int main()
{
   
   
    int x, y;
    memset(dp,0,sizeof(dp));
    memset(vis,0,sizeof(vis));
    cin >> n;
    for(int i=1;i<=n;i++)
    {
   
   
        fa[i]=i;
        dp[i][0]=0;

    }
    for(int i=1;i<=n;i++)
        cin >> dp[i][1];

    while(cin>>x>>y)
    {
   
   
        if(x==0&&y==0)
            break;
        fa[x]=y;
    }
    int ff;
    for(int i=1;i<=n;i++)
    {
   
   
        if(fa[i]==i)
        {
   
   
            ff = i;
            dfs(i);
            break;
        }
    }
    cout << max(dp[ff][0],dp[ff][1]) <<endl;
    return 0;
}


2 . P2015 二叉苹果树
题目描述
有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)

这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。

我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树

2 5
\ /
3 4
\ /
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

给定需要保留的树枝数量,求出最多能留住多少苹果。

输入输出格式
输入格式:
第1行2个数,N和Q(1<=Q<= N,1<N<=100)。

N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。

每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。

每根树枝上的苹果不超过30000个。

输出格式:
一个数,最多能留住的苹果的数量。

输入输出样例
输入样例#1:
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出样例#1:
21

dp[i][j];//以i为根的有j个树枝的最大苹果数

如果i要选用它的儿子的苹果,那么就必须加上i和j这个树枝上的苹果(e[i][j])

代码:
// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int n, q;
int e[110][110];
int dp[110][110];//以i为根的有j个树枝的最大苹果数
int vis[110];

void dfs(int x)
{
   
   
    vis[x]=1;
    for(int i=2;i<=n;i++)
    {
   
   
        if(!vis[i]&&e[i][x]!=-1)
        {
   
   
            dfs(i);
            for(int j=q;j>=1;j--) //这里如果j从1遍历到q,那么由于dp[x][j]=max(dp[x][j],dp[x][k]+dp[i][j-k-1]+e[i][x]);dp[x][j]的值会受dp[x][小于j]的影响,而此时dp[x][小于j]有可能已经包含i的树枝了,这样就会有所重叠,
            {
   
   
                for(int k=0;k<j;k++)
                {
   
   
                    dp[x][j]=max(dp[x][j],dp[x][k]+dp[i][j-k-1]+e[i][x]);
                }
            }
        }
    }
}

int main()
{
   
   
    memset(e,-1,sizeof(e));
    memset(dp,0,sizeof(dp));
    memset(vis,0,sizeof(vis));
    int a, b, w;
    cin >> n >> q;
    for(int i=1;i<n;i++)
    {
   
   
        cin >> a >> b >> w;
        e[a][b]=e[b][a]=w;
    }
    dfs(1);
    cout << dp[1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值