[SMOJ1793]选课

来源:互联网 发布:充值软件下载 编辑:程序博客网 时间:2024/05/22 12:49

题目描述

学校实行学分制。每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。学校开设了 NN<300)门的选修课程,每个学生可选课程的数量 M 是给定的。学生选修了这 M 门课并考核通过就能获得相应的学分。

在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修。例如《Frontpage》必须在选修了《Windows操作基础》之后才能选修。我们称《Windows操作基础》是《Frontpage》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。每门课都有一个课号,依次为 1,2,3,…。 例如:

表中 1 是 2 的先修课,2 是 3、4 的先修课。如果要选 3,那么 1 和 2 都一定已被选修过。   你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。

输入格式 1793.in

输入文件的第一行包括两个整数 NM(中间用一个空格隔开)其中 1N300,1MN

以下 N 行每行代表一门课。课号依次为 1,2,,N。每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为 0),第二个数为这门课的学分。学分是不超过 10 的正整数。

输出格式 1793.out

输出文件每行只有一个数。第一行是实际所选课程的学分总数。

输入样例 1793.in

输入样例一:
5 4
0 1
1 1
2 3
0 3
2 4
输入样例二:
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2

输出样例 1793.out

输出样例一:
9
输出样例二:
13


这道题是树型 DP + 背包,也属于比较经典的题目。

题目大意:有一片森林(若干棵树),共 n 个结点,每个结点有权值 vi。要求从中选 m 个点,且所选点的父亲也必须被选中(除每棵树的根结点外),使所选点权值之和最大,求这个最大的权值和。

我们不妨简化一下题目,不考虑森林,也不考虑多叉,先从一棵二叉树的情况入手。

如图是一棵二叉树,我们要从中选一些点。如果我们要选 2 或 3,则必须先选 1,其他的类似。
不妨定义 f[root][i] 表示在以 root 为根的子树中选 i 个结点所得的最大权值和。

首先,对于所有的 f[root][i],根结点 root 都是必须选的,否则它的儿子们根本没办法选。
而对于每一个 i,当确定了左儿子选 j 个点时,也就知道了右儿子要选 ij1 个点。
例如,在上面的图中,f[1][8]=v1+max{f[2][x]+f[3][7x]}

于是对于每一个问题都分别递归求解子问题,再枚举合并一下就好了,边界条件则是对于所有的叶子结点,f[root][1]=vroot
时间复杂度为 O(n×m2)

再来看一棵多叉树的情况。

我们不妨从左到右依次考虑每个子问题。例如对于以 1 为根结点时,要选 x 个点的问题。
显然 1 的儿子们总共要选 x1 个点,但是我们并不知道每棵子树会选多少个,有可能全选,也可以一个都不选。
怎么办呢?

枚举一下就好了。

既然我们是从左到右依次考虑的,那么例如我们在考虑 3 的时候,已经充分考虑过以 2 为根的子树对 1 的影响,也就是说整棵树可以大致分为两部分:

于是我们就可以枚举在以 3 为根的子树中取多少个点,然后它去改进已考虑。
写成状态转移方程就是

g[root][i]=max{f[root][i],g[son][j]+f[root][ij]}

其中 f 是已考虑,g 是待考虑。每考虑完一棵子树之后就将 g 复制到 f 中。
其实此题跟“修复道路”是有相似之处的,都属于在树上做背包的情形。于是同理,也可以通过从大到小枚举 j 的方法,省掉一个滚动数组,直接做。

但是不要忘了,本题是森林而不是一棵多叉树。虽然分开求解最后再枚举合并也可以,但是更巧妙的方法则是加一个点 0,作为森林中每棵树的根结点的父亲,它的权值为 0,而所选点数要加上 1,即可。这样就将森林变成了一棵多叉树。

尽管是多叉,时间复杂度仍然是 O(n×m2)

参考代码:

#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int maxn = 1e3;int n, m;int score[maxn];int head[maxn];struct Edge { int to, next; } edge[maxn];int cnt = 0;void addEdge(int u, int v) {    edge[++cnt].to = v;    edge[cnt].next = head[u];    head[u] = cnt;}int dp[maxn][maxn];void dfs(int root, int pre) {    dp[root][0] = 0;    for (int i = 1; i <= m; i++) dp[root][i] = score[root];    for (int i = head[root]; i; i = edge[i].next)        if (edge[i].to != pre) {            dfs(edge[i].to, root);            for (int j = m; j > 1; j--)                for (int k = 1; k < j; k++)                    dp[root][j] = max(dp[root][j], dp[edge[i].to][k] + dp[root][j - k]);        }}int main(void) {    freopen("1793.in", "r", stdin);    freopen("1793.out", "w", stdout);    scanf("%d%d", &n, &m); ++m;    for (int i = 1; i <= n; i++) {        int father;        scanf("%d%d", &father, &score[i]);        addEdge(father, i);    }    dfs(0, 0);    printf("%d\n", dp[0][m]);    return 0;}
0 0
原创粉丝点击