1 条题解

  • 0
    @ 2025-8-24 21:32:57

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar He_Ren
    Stay hungry, stay foolish.

    搬运于2025-08-24 21:32:57,当前版本为作者最后更新于2019-07-04 17:11:40,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目传送门

    首行求赞,反正不FA钱 qwq\ \text{qwq}

    0. 前置

    • 动态规划基础
    • 知道树是什么,以及存图方式

    1. 题意简述

    • 一堆树构成的森林,共NN个点。
    • 每个点有一个权值sis_i
    • 一个点可以被选择,当且仅当它到根节点的路径上的所有点都被选择。

    共选择MM个点,求被选择的点的权值和的最大值

    2. 一个小技巧

    我们发现,如果0算一个节点的话,整张图就是一棵树了

    这样的好处:

    1. 一棵树就不用分别考虑各棵树然后合并了
    2. 输入方便很多,不用特别处理00的情况

    但是MM就会受影响

    因为根节点00是必选的,所以只要让MM增加11就好了


    确保您理解了以上内容再看下面的部分,如果不理解可以自己画图体会


    3. dp及初步设计状态

    首先,不难看出,父节点的信息可以由子节点合并得到并且不会影响子节点

    所以使用 dp/记忆化搜索 就好了 qwq\ \text{qwq}

    不难想到,用dp[u][i]\text{dp[u][i]}表示以节点uu为根的子树 ,选择ii个点可以获得的最大权值和

    然后想如何转移

    。。。好像遇到麻烦了

    显然合并子节点的信息一定能得到父节点的信息,但使用简单的算法好像不行了

    没事反正数据范围小

    可以考虑dp套dp(当然这个题算比较简单的)

    4. 背包

    继续观察,发现每个子节点都会占用父节点ii的一部分,又有一个贡献,可以选择或不选择

    重量。。。价值。。。总重。。。

    这不是01背包吗?

    不同之处在于,每个子节点的重量都是变量

    重新设计状态

    dp[u][i][j]dp[u][i][j]表示节点uu的前ii个子节点,限重为jj能得到的最大权值和(价值和)

    像01背包一样压缩空间,得到:

    dp[u][j]表示节点u,限重j的最大权值和(价值和)

    代码:

    for(int i=head[u]; i; i=e[i].next)//遍历所有子节点
    	for(int j=m, v=e[i].to; j>0; --j)//这里和01背包一样,总重从大到小循环
    		for(int k=0; k<j; ++k)//这里是不同之处,子节点的重量需要规定
    			chk_max(dp[u][j], dp[u][j-k]+dp[v][k]);//此函数是将前一个参数设为二者的最大值(您不会不知道吧),不明白下面有代码
    
    1. code
    #include<cstdio>
    const int MAXN = 300 + 5;
    const int MAXM = 300 + 5;
    
    inline void chk_max(int &a,int b){ if(a<b) a=b;}
    
    //前向星存图
    struct Edge
    {
    	int next,to;
    }e[MAXN];
    int head[MAXN],ecnt=0;
    inline void add(int u,int v)
    {
    	++ecnt;
    	e[ecnt].next=head[u];
    	e[ecnt].to=v;
    	head[u]=ecnt;
    }
    
    int m;
    int dp[MAXN][MAXM];
    
    void solve(int u)
    {
    	for(int i=head[u]; i; i=e[i].next)
    		solve(e[i].to);//先处理子节点
    	
        //背包部分
    	for(int i=head[u]; i; i=e[i].next)
    		for(int j=m, v=e[i].to; j>0; --j)
    			for(int k=0; k<j; ++k)
    				chk_max(dp[u][j], dp[u][j-k]+dp[v][k]);
    }
    
    int main(void)
    {
    	
    	int n;
    	scanf("%d%d",&n,&m);
    	++m;//上文提到了
    	for(int i=1; i<=n; ++i)
    	{
    		int fa;
    		scanf("%d%d",&fa,&dp[i][1]);//思考:为什么直接用dp[i][1]?
    		add(fa,i);
    	}
    	
    	solve(0);
    	printf("%d",dp[0][m]);
    	return 0;
    }
    

    本题解主要用于理清自己思路,如果有与他人方法重复也不要喷qaq

    实际还写了蛮长时间的。。。码字手酸,见谅qwq

    如果您觉得题解有错误,可以私信或者评论

    当然别忘了点赞qwq

    • 1

    信息

    ID
    979
    时间
    1000ms
    内存
    125MiB
    难度
    4
    标签
    递交数
    2
    已通过
    1
    上传者