选课 ( dp 树形dp 动态规划 树规)
和某篇随笔重了?!!?!?!?!?!?不管了留着吧
题目:
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?
输入
第一行有两个整数N,M用空格隔开。(1<=N<=200,1<=M<=150)
接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。
输出
只有一行,选M门课程的最大得分。
样例输入
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
样例输出
13
题解
多叉树转二叉树。树形dp。
1.多叉树转二叉树转法:
我们用到的是孩子兄弟法。
对于每一个节点,他在二叉树中的左孩子是在多叉树中的第一个孩子,他在二叉树中的右孩子是在多叉树中的第一个兄弟。
如上图,左边的是原来的多叉树,右边的是二叉树,其中红色线表示左孩子的关系,绿色线表示右孩子的关系。
比如节点2,在多叉树中的第一个孩子是6,那么他在二叉树中的左孩子是6。第一个兄弟是3,所以在二叉树中的右孩子是3。
为什么我们要转换呢?因为方便记忆化搜索处理状态啊!
因为输入的是一个森林,我们就通过节点0来代表一棵树的根的父亲(这是很方便的,因为输入数据就是这样的:若ki=0表示没有直接先修课)。
------------------------------------------------------------------
2.然后是树形dp。我这里用的记忆化搜索。
dfs(i,j)代表当前要考虑第i个课程及以其为根的二叉树中的子树的选择情况,j表示当前能够选择多少门课程。
根据题意可知,现在正在考虑第i门课程的情况时,必须已经选择了第i门课程在多叉树中的爹(要不选个毛啊)。所以我们可以直接略过第i门课程,去考虑第i门课程的右孩子(多叉树中的下一个兄弟),当然也不能考虑自己的左孩子(多叉树中的孩子)了。
我们还需要选第i门课程的情况。选第i门课程,就得加上这门课程的学分,然后剩余总课程--。剩下的课程可以分给自己的左孩子(多叉树中的孩子)一些,剩下的分给自己的右孩子(多叉树中的兄弟)。在dfs中,for(int k=0;k<j;k++)这里面的这个k就是给左孩子多少。那么就更新答案为。自己的学分+dfs左孩子的学分+dfs右孩子的学分。
简洁的main函数就是读入->转换->dfs并输出答案。这里我们不dfs 0,而是直接dfs 0的左孩子(之前说过0是整个森林的根),是因为防止在边界判断时候把0判断掉。
3.代码
#include <iostream>
using namespace std;
int n,m;
int a[2001],f[2001];//a[i]是某一门课程的学分 f[i]是某一门课程在多叉树中的爹
int lChild[2001],rChild[2001];//二叉树的左子树和柚子树
int dp[2001][2001];//dp[i][j]是以第i个节点为根的二叉树(注意是二叉树),能选择j门课程时候的最大学分。
void convert()//多叉树 -> 二叉树
{
for(int i=1;i<=n;i++)//遍历每一个节点
{
int fa=f[i];//这个节点的爹
if(lChild[fa]==0)lChild[fa]=i;//这个节点的爹还没有孩子,所以这个节点就是这个节点的爹的第一个孩子
else//这个节点的爹有孩子,所以这个节点在二叉树中就应该是这个节点的爹的左孩子的最右边的叶子的孩子
{
fa=lChild[fa];//暂时让变量fa变成爹的第一个孩子(二叉树中的左孩子)
while(rChild[fa])fa=rChild[fa];//循环找最右边的叶子
rChild[fa]=i;//成为最右边的叶子的右孩子
}
}
}
int dfs(int i,int j)//i是当前节点的编号,j是当前可以选择的课程数目
{
if(i<1||j<1||i>n||j>m)return 0;//边界判断
if(dp[i][j]!=0)return dp[i][j];//查备忘录
for(int k=0;k<j;k++)//选给左子树k个,自己留一个(得选自己,否则无法选左孩子),剩下的给右
dp[i][j]=max(dp[i][j],a[i]+dfs(lChild[i],k)+dfs(rChild[i],j-k-1));//自己的学分+dfs左孩子的学分+dfs右孩子的学分
dp[i][j]=max(dp[i][j],dfs(rChild[i],j));//略过自己,都给右孩子
return dp[i][j];//最后返回答案
} int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++)
cin >> f[i] >> a[i];
convert();
cout << dfs(lChild[0],m) << endl;
return 0;
}
选课 ( dp 树形dp 动态规划 树规)的更多相关文章
- C++ 洛谷 2014 选课 from_树形DP
洛谷 2014 选课 没学树形DP的,看一下. 首先要学会多叉树转二叉树. 树有很多种,二叉树是一种人人喜欢的数据结构,简单而且规则.但一般来说,树形动规的题目很少出现二叉树,因此将多叉树转成二叉树就 ...
- CH5402 选课【树形DP】【背包】
5402 选课 0x50「动态规划」例题 描述 学校实行学分制.每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分.学校开设了 N(N≤300) 门的选修课程,每个学生可选课程的数量 M 是 ...
- joyOI 选课 【树形dp + 背包dp】
题目链接 选课 题解 基础背包树形dp #include<iostream> #include<cstdio> #include<cmath> #include&l ...
- 洛谷$2014$ 选课 背包类树形$DP$
luogu Sol 阶段和状态都是树形DP板子题,这里只讲一下背包的部分(转移)叭 它其实是一个分组背包模型,具体理解如下: 对于一个结点x,它由它的子结点y转移而来 在子结点y为根的树中可以选不同数 ...
- luogu2014 选课 背包类树形DP
题目大意:有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b).一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少? ...
- codevs 1378 选课 (树形DP)
#include<iostream> #include<cstdio> #include<cstring> using namespace std; ][],f[] ...
- 选课(树形DP)
题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学分,每门课有一 ...
- cogs 1199选课(树形dp 背包或多叉转二叉
http://cogs.pro:8080/cogs/problem/problem.php?pid=vQyiJkkPP 题意:给m门课,每门课在上完其先修课后才能上,要你从中选n门课使得总学分尽可能大 ...
- [Luogu2014]选课(树形dp)
[Luogu2014]选课 题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课 ...
随机推荐
- 深入VR之前 你应该知道VR头显透镜原理
转自:http://www.gamelook.com.cn/2016/03/246817 要理解虚拟现实头显透镜的工作原理,首先要搞懂眼睛是如何看到事物的. 眼睛瞳孔后有晶状体,也就是眼珠子.眼睛的背 ...
- C语言-数组
数组是具有同一属性的若干个数据组织成一个整体,互相关联 数组是有序数据的集合.数组中的每一个元素都属于同一个数据类型,用一个统一的数组名和下标来唯一地确定数组中的元素 一维数组 一维数组的定义 在定义 ...
- 问题:sqlserver 跨服务器连接;结果:Sql Server 跨服务器连接
Sql Server 跨服务器连接 用openrowset连接远程SQL或插入数据 --如果只是临时访问,可以直接用openrowset --查询示例 select * from openrowset ...
- PLSQL语法
Procedural Language和SQL的结合体.通过增加变量.控制语句,使我们可以写些逻辑更加复杂的数据库操作 语句框架组成 declare – 可选 声明各种变量或游标的地方. begin ...
- 批处理基本知识以及进阶 V2.0
批处理基本知识以及进阶 将以要执行的程序指令 , 像在 dos 模式下一下写入记事本 , 保存成 bat 文件 , 就可以执行了 一 . 简单批处理内部命令简介 1.Echo 命令 打开回显或关闭请求 ...
- 线程池的原理以及实现线程池的类ExecutorService中方法的使用
1.线程池:线程池就是就像一个容器,而这个容器就是用来存放线程的,且有固定的容量. 如果没有线程池,当需要一个线程来执行任务时就需要创建一个线程,我们设创建线程的时间为t1,执行线程的时间为t2,销毁 ...
- NSButton添加事件
-(void)addButton { NSButton* pushButton = [[NSButton alloc] initWithFrame: NSMakeRect(, , , )]; push ...
- Json Post到 https的坑 - the underlying connection was closed an unexpected error occurred on a send(远程服务器未知错误导致关闭)
最近做了一个安装包,安装包会弹出dotnet的 窗体,这个安装包会去调用https的一个api.用测试程序测试窗体都是好的.一旦打入安装包后,就报错.研究了半天,原来是https惹的祸 解决方案: . ...
- Redis面试考点
什么是Redis? Redis 是一个基于内存的高性能key-value数据库. Redis的特点以及缺点? Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据 ...
- java全栈day01-02入门案例
一 在开始案例之前,我们需要了解一下Java应用程序的编写流程. 通过上图我们可以了解到编写的程序大致如下: 1 源文件:编写Java源文件(我们也称之为源代码文件),它的扩展名为.java: 2 ...