• [CTSC1997]选课 | 树形背包&多叉树转二叉树


    题目描述

    在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 (N) 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 (a) 是课程 (b) 的先修课即只有学完了课程 (a),才能学习课程 (b))。一个学生要从这些课程里选择 (M) 门课程学习,问他能获得的最大学分是多少?

    输入格式

    第一行有两个整数 (N,M) 用空格隔开。( (1 leq N leq 3001≤N≤300 , 1 leq M leq 3001≤M≤300) )

    接下来的 (N) 行,第 (I+1) 行包含两个整数 (k_i)(s_i, k_i)

    (k_i) 表示第 (i) 门课的直接先修课,(s_i) 表示第 (i) 门课的学分。若 (k_i=0) 表示没有直接先修课((1 leq {k_i} leq N, 1 leq {s_i} leq 20))

    输出格式

    只有一行,选 (M) 门课程的最大得分。

    样例

    输入 #1

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

    输出 #1

    13

    ————————————————————————————————————————————

    解法一:树形背包DP

    由于课程间的拓扑关系,组成的树是一颗 DAG 树,很适合进行动态规划。

    (dp[i][j][k]) 为在以节点 (i) 为根的子树中,前 (j) 个节点(自己为第一个),包括 (i) 自己共选 (m) 课的最大学分

    此时节点 (i) 和它的第一层子节点一起构成了一个一维背包

    同背包 DP 一样,中间的 (j) 可以由滚动数组优化掉,第一重循环需要倒序一下,第二重循环则不需要考虑顺序问题

    代码如下:

    #include <bits/stdc++.h>
    #define MAXN 307
    using namespace std;
    int n,m;
    int s[MAXN],f[MAXN][MAXN];
    vector<int> G[MAXN];
    void Tree(int u) {
            // 此时节点 u 和它的第一层子节点一起构成了一个一维背包
    	f[u][1]=s[u]; 
    	for (int k=0;k<(int)G[u].size();k++) {
    		int v=G[u][k]; Tree(v);
    		for (int i=m;i>=1;i--) // 滚动数组优化后需要倒序
    			for (int j=0;j<=i-1;j++) // 这个循环不需要考虑顺序问题
    				f[u][i]=max(f[u][i],f[v][j]+f[u][i-j]); 
    	}
    }
    int main() {
    	memset(f,0,sizeof(f));
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++) {
    		int k; scanf("%d%d",&k,&s[i]);
    		G[k].push_back(i);
    	}
    	m++,Tree(0); // 注意加入了 0 号节点后 m 要 + 1
    	printf("%d",f[0][m]);
    	return 0;
    } 
    

    解法二:多叉树转二叉树后DP

    如果不想在一棵树上做背包,也可以使用多叉树转成二叉树的方法,这样只要确定分配给左节点的值就可以推断出分配给右节点的值了

    多叉树转二叉树的大致思想是:左儿子,右兄弟

    在本题中,原先的兄弟可能变成了右儿子,而这些兄弟在原拓扑关系中是平级的,也就是说当前节点直接可以继承右节点的值,不需要自取(同时左节点也不能取)

    对于左节点能取的情况,当前节点必取,于是枚举分配给左节点的课程数,右边的课程数,取一个最优值即可

    注意这种由多叉树转换的二叉树深度会很深,必须 使用二叉链来存储,使用数组将会空间不足(比如下面的代码)。

    代码如下:

    #include <bits/stdc++.h>
    #define MAXN 3007
    using namespace std;
    int n,m,s[MAXN],tr[MAXN];
    int pt[MAXN],f[MAXN][MAXN];
    vector<int> G[MAXN];
    void convert(int u) {
    	int sz=(int)G[u].size();
    	if (!sz) return;
    	// 插入到左节点
    	int v=pt[u]<<1;
    	tr[v]=G[u][0],pt[G[u][0]]=v;
    	convert(G[u][0]);
    	// 其他插入右节点 
    	for (int i=1;i<(int)G[u].size();i++) {
    		v=(v<<1)+1,tr[v]=G[u][i];
    		pt[G[u][i]]=v,convert(G[u][i]);
    	}
    }
    void Tree(int u) {
    	if (tr[u]==-1) return;
    	Tree(u<<1),Tree((u<<1)+1);
    	for (int i=1;i<=m+1;i++) {
    		if (f[(u<<1)+1][i]) f[u][i]=f[(u<<1)+1][i];
    		for (int j=0;j<i;j++) {
    			f[u][i]=max(f[u][i],f[u<<1][j]+f[(u<<1)+1][i-j-1]+s[tr[u]]);
    		}
    	}
    }
    int main() {
    	memset(tr,-1,sizeof(tr));
    	memset(f,0,sizeof(f));
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++) {
    		int k; scanf("%d%d",&k,&s[i]);
    		G[k].push_back(i);
    	}
    	pt[0]=1,tr[1]=0,convert(0),Tree(1);
    	printf("%d",f[1][m+1]);
    	return 0;
    }
    
  • 相关阅读:
    Div在BOdy中居中
    c_lc_填充每个节点的下一个右侧节点指针 I~II(递归)
    c_pat_哈密顿回路 & 最大点集 & 是否是旅行商路径 & 欧拉路径 & 最深的根(邻接矩阵存图)
    c_lc_二叉搜索树的最近公共祖先 & 二叉树的最近公共祖先(利用性质 | 从p,q开始存储每个结点的父亲)
    c_pat_树题大杂烩(利用性质)
    现在的我,理解了这种「激情」
    b_pat_排成最小的数字 & 月饼(字符串拼接比较a+b<b+a)
    c_lc_二叉搜索树中的众数(中序遍历+延迟更新前驱结点)
    b_pat_分享 & 链表排序 & 链表去重(链表模拟)
    b_pat_弹出序列(栈模拟)
  • 原文地址:https://www.cnblogs.com/zhwer/p/14664172.html
Copyright © 2020-2023  润新知