luogu2014 选课

【题目描述】

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

【输入格式】

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

接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。

【输出格式】

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

【代码】

1. 多叉转二叉

 1 /*
 2 User:Mandy.H.Y
 3 Language:c++
 4 Problem:course
 5 */ 
 6 #include<bits/stdc++.h>
 7 using namespace std;
 8 const int maxn = 320;
 9 int f[maxn][maxn] , bro[maxn] , son[maxn], v[maxn];
10 void add(int fa, int s)
11 //类似链表的存储 
12 {
13     bro[s] = son[fa];
14     //son[i]中存入i的第一个儿子 
15     son[fa] = s;
16     //bro[i]中存入i的上一个兄弟 
17 }
18 
19 int dp(int i, int j)
20 //对于每一个i节点,
21 //定义dp(i,j)为i的所有兄弟和 i 的所有儿子,和 i 自己,学 j 门课的最大学分总和。
22 {
23     if (i==-1) return 0;
24     if (j==0) return 0;
25     if (f[i][j] != -1) return f[i][j];
26     //记忆化 ,必备,在下面循环中son与bro有些会重算 
27     int m = -1<<30; 
28     //最小值 
29 
30     // 全分兄弟
31     m = max( m, dp(bro[i] , j));
32 
33     for (int k = 0; k <= j-1; k++)
34     //从0开始,表示不选 儿子,选i自己与j-1个兄弟 
35     {
36         m = max( m , dp(son[i] , k) + dp(bro[i] , j-1-k) + v[i]);
37         /*那么,可以分成两种情况:
38           1、不学 i 这门课,全部学兄弟的课程,dp( i , j ) = dp( bro[ i ] , j)     
39           2、学 i 以及以 i 为先修课的课程, dp( i , j ) = dp( bro[ i ] , j - 1 - k ) + dp( son[ i ] , k ) + v[ i ]*/
40     }
41     f[i][j] = m;
42     return m;
43 }
44 int main()
45 {
46     memset(son , -1, sizeof(son));
47     memset(bro , -1, sizeof(bro));
48     memset(f   , -1, sizeof(f  ));
49     //初始化,必备,dfs中要用于判断 、,用0易混淆,易错 
50     int n, m;
51     cin>>n>>m;
52     for(int i=1;i<=n;i++){
53         int fa,vx;
54         cin>>fa>>vx;
55         add(fa,i);
56         v[i] = vx;
57     }
58     cout<<dp(0, m+1);
59     return 0;
60 }

2. 背包

 1 /*
 2 User:Mandy.H.Y
 3 Language:c++
 4 Problem:course
 5 */ 
 6 #include<bits/stdc++.h>
 7 using namespace std;
 8 
 9 int m,n,head[305],next1[305],f[305][305];
10 
11 void readdata()
12 {
13     scanf("%d%d",&n,&m);
14     for(int i=1;i<=n;i++)
15     {
16         int a;
17         scanf("%d%d",&a,&f[i][0]);
18         //a是第i门的直接先修课
19         //当a等于0时,无父亲的节点便接到0节点上,使树有且只有一个根 
20         next1[i]=head[a];
21         head[a]=i;
22     }
23 }
24 
25 void init()
26 {
27     freopen("cour.txt","r",stdin);
28     freopen("cour.txt","w",stdout);
29 }
30 
31 int deep(int x)
32 {
33     if(head[x]==0) return 0;
34     int zi=0;
35     //指已算过的科目的总数 
36     for(int i=head[x];i!=0;i=next1[i])
37     {
38         int izi=deep(i);
39         //t表示i的子结点的个数 
40         zi=zi+izi+1;
41         for(int j=zi;j>=0;j--)
42         //01背包,一定要是降序,否则可能一个科目选两遍
43             for(int k=0;k<=izi;k++)
44                 if(j-k-1>=0&&f[x][j-k-1]+f[i][k]>f[x][j])  f[x][j]=f[x][j-k-1]+f[i][k];
45                 //如果(还有空间选i及i的k个子结点)
46     }
47     return zi;
48 }
49 
50 void work()
51 {
52         deep(0);
53         printf("%d",f[0][m]);
54 }
55 
56 int main()
57 {
58     //init();
59     readdata();
60     work();
61     return 0;
62 }

 

posted @ 2019-08-15 08:38  Mandy_H_Y  阅读(164)  评论(0编辑  收藏  举报