树形DP学习笔记

概念:

树形dp就是....我也不知道是什么,反正一个主件下面有很多的附件可选就是树形dp,咕咕咕

dp方程 (并不适用于全部的树形dp)

树形dp的主要实现形式是dfs,在dfs中dp,主要的实现形式是dp[i][j](j=0或者是1),i是以i为根的子树,j是表示在以i为根的子树中选择j个子节点,0表示这个节点不选,1表示选择这个节点。有的时候j或0/1这一维可以压掉

选择节点类:
$$dp[i][0]=dp[j][1]
dp[i][1]=max/min(dp[j][0],dp[j][1])$$

树形背包类:
$$dp[v][k]=dp[u][k]+val
dp[u][k]=max(dp[u][k],dp[v][k−1])$$

其实我觉得就是这样的:
dp[i][j]并不表示以i为根,选j个物品,而是从根节点到i的路径及其左边所有的节点,以及以i为根的子树的所有节点中,容量为j的最大价值。上述方程应该用到了泛化物品的方法

泛化物品:

定义
考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。

更严格的定义之。在背包容量为V的背包问题中,泛化物品是一个定义域为0..V中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。

这个定义有一点点抽象,另一种理解是一个泛化物品就是一个数组h[0..V],给它费用v,可得到价值h[V]。

一个费用为c价值为w的物品,如果它是01背包中的物品,那么把它看成泛化物品,它就是除了h(c)=w其它函数值都为0的一个函数。如果它是完全背包中的物品,那么它可以看成这样一个函数,仅当v被c整除时有h(v)=v/cw,其它函数值均为0。如果它是多重背包中重复次数最多为n的物品,那么它对应的泛化物品的函数有h(v)=v/cw仅当v被c整除且v/c<=n,其它情况函数值均为0。

一个物品组可以看作一个泛化物品h。对于一个0..V中的v,若物品组中不存在费用为v的的物品,则h(v)=0,否则h(v)为所有费用为v的物品的最大价值。P07中每个主件及其附件集合等价于一个物品组,自然也可看作一个泛化物品。

泛化物品的和
如果面对两个泛化物品h和l,要用给定的费用从这两个泛化物品中得到最大的价值,怎么求呢?事实上,对于一个给定的费用v,只需枚举将这个费用如何分配给两个泛化物品就可以了。同样的,对于0..V的每一个整数v,可以求得费用v分配到h和l中的最大价值f(v)。也即

f(v)=max{h(k)+l(v-k)|0<=k<=v}

可以看到,f也是一个由泛化物品h和l决定的定义域为0..V的函数,也就是说,f是一个由泛化物品h和l决定的泛化物品。

由此可以定义泛化物品的和:h、l都是泛化物品,若泛化物品f满足以上关系式,则称f是h与l的和。这个运算的时间复杂度取决于背包的容量,是O(V^2)。

泛化物品的定义表明:在一个背包问题中,若将两个泛化物品代以它们的和,不影响问题的答案。事实上,对于其中的物品都是泛化物品的背包问题,求它的答案的过程也就是求所有这些泛化物品之和的过程。设此和为s,则答案就是s[0..V]中的最大值。

背包问题的泛化物品
一个背包问题中,可能会给出很多条件,包括每种物品的费用、价值等属性,物品之间的分组、依赖等关系等。但肯定能将问题对应于某个泛化物品。也就是说,给定了所有条件以后,就可以对每个非负整数v求得:若背包容量为v,将物品装入背包可得到的最大价值是多少,这可以认为是定义在非负整数集上的一件泛化物品。这个泛化物品——或者说问题所对应的一个定义域为非负整数的函数——包含了关于问题本身的高度浓缩的信息。一般而言,求得这个泛化物品的一个子域(例如0..V)的值之后,就可以根据这个函数的取值得到背包问题的最终答案。

综上所述,一般而言,求解背包问题,即求解这个问题所对应的一个函数,即该问题的泛化物品。而求解某个泛化物品的一种方法就是将它表示为若干泛化物品的和然后求之。(以上内容取自背包九讲

1660941-20190509094137505-2062712628.png
上图取自国家集训队论文《浅谈几类背包题》,点击图片可以放大.

例题:

一.P2014 选课

传送门

思路

题目意思是想选择一门课,必须要先学会它的必修课,即选择一门课必须要选择必修课。那么他又说要选择的价值最大,这就是背包问题啦。又因为他有依赖性性行为,所以就是个树形dp
解释一下样例:

7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2

1660941-20190509094727710-2086752185.png

很明显此题的关联关系一目了然,所以我们可以在子树上适用范化背包.
注意爸爸是0说明没有爸爸
即:
设f[i][j]表示选择以i为根的子树中j个节点。
u代表当前根节点,sum代表其选择的节点的总额。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
#define ll long long int
#define MAXN 2500
using namespace std;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
struct node {
    int next,to;
} e[MAXN];
int n,m,head[MAXN],c[MAXN],f[MAXN][MAXN],sum;
void add(int x,int y)
{
    e[++sum].next=head[x];  
    e[sum].to=y;
    head[x]=sum;
}
void build(int u,int t)
{
    if(t<=0) return;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        for(int j=0;j<t;++j)
        {
            f[v][j]=f[u][j]+c[v];
        }
        build(v,t-1);
        for(int k=1;k<=t;++k)
        {
            f[u][k]=max(f[u][k],f[v][k-1]);
        }
    }
}
int main() {
    n=read(),m=read();
    for(int i=1; i<=n; ++i) 
    {
        int a;
        cin>>a>>c[i];
        if(a!=0) 
        {
            add(a,i);
        }
        if(a==0) 
        {
            add(0,i);
        }
    }
    build(0,m);
    cout<<f[0][m];
    return 0;
}

二.P1352 没有上司的舞会

链接:

传送门

思路:

这道题比上面的简单多了,咕咕咕,就是直接用第一个dp方程

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<string>
#include<cstring>
#define ll long long int
#define MAXN 6666
using namespace std;
const int maxn=999999999;
const int minn=-999999999;
inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while(c < '0' || c > '9') {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
/*
注释掉的有bug 
struct node {
    int next,to;
    int in;
} e[MAXN];
int n,m,happy[MAXN],head[MAXN],dp[MAXN][MAXN],root,sum;
void add(int x,int y) {
    e[++sum].next=head[x];
    e[sum].to=y;
    head[x]=sum;
}
void do_it(int x) {
    for(int i=head[x]; i; i=e[i].next) {
        do_it(i);
        dp[x][1]=max(max(dp[x][1],dp[x][1]+dp[i][0]),dp[i][0]);
        dp[x][0]=max(max(dp[x][0],dp[i][1]+dp[x][0]),max(dp[i][1],dp[i][0]));
    }
}
int main() {
    n=read();
    for(int i=1; i<=n; ++i ) happy[i]=read();
    for(int i=1; i<=n-1; ++i) {
        int x,y;
        cin>>x>>y;
        e[x].in++;
        add(y,x);
    }
    int zero1,zero2;//读掉0
    cin>>zero1>>zero2;
    for(int i=1; i<=n; ++i) {
        if(!e[i].in) {
            root=i;
            break;
        }
    }
    do_it(root);
    cout<<max(dp[root][0],dp[root][1]);
    return 0;
}
注释掉的有bug 
*/
int ind[MAXN],n,hap[MAXN],dp[MAXN][2],fa[MAXN],root,vis[MAXN],next[MAXN],po[MAXN];
void do_it(int x)
{
    for(int i = po[x]; i; i = next[i])
    {
        do_it(i);
        dp[x][1]=max(max(dp[x][1],dp[x][1]+dp[i][0]),dp[i][0]);
        dp[x][0]=max(max(dp[x][0],dp[i][1]+dp[x][0]),max(dp[i][1],dp [i][0]));
    }
}
int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>dp[i][1];
    for(int i=1; i<=n; i++)
    {
        int a,b;
        cin>>b>>a;
        ind[b]++;
        next[b]=po[a];
        po[a]=b;
    }
    for(int i=1; i<=n; i++)
        if(!ind[i])
        {
            root=i;
            break;
        }
    do_it(root);
    cout<<max(dp[root][0],dp[root][1]);
}

这篇博客是我学习了网上的大佬和国家队徐持衡的论文后写的,奈何我的水平太低无法深入的学会,只是肤浅的懂了所以可能会有一些错误,如果发现请评论区告诉我!

转载于:https://www.cnblogs.com/pyyyyyy/p/10836678.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值