题目描述
【问题描述】
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有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
题解:
首先要把多叉树转换为二叉树 :遵循原则:左节点为儿子,右节点为兄弟。d[x],l[],r[]分别表示 x结的最远儿子节点,左节点右节点。
inline void ins(int x,int f){//x为子节点,f为父节点
if( !d[f] ) l[f]=x;
else r[d[f]]=x;
d[f]=x;
}
题目要选若干点使得分最大,若选子节点就必须选父节点,将多叉树转为二叉树后,问题就转换为 以x为节点保留y个节点的最大值。
所以设
f[x][y] 以x为根保留y个节点的最大值 f[x][y]=max{ f[ r[x] ][ y ] , f[ l[x]
][k]+f[r[x]][y-k-1]+c[x] } k=[0,y-1 ]
f[0][m+1] 即为答案。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1100;
int f[MAXN][MAXN],c[MAXN],l[MAXN],r[MAXN],d[MAXN];
int n,m;
inline void ins(int x,int f){//x为子节点,f为父节点
if( !d[f] ) l[f]=x;
else r[d[f]]=x;
d[f]=x;
}
int dp(int x,int y){
if(x<0 || y<0) return 0;
if(f[x][y]!=-1) return f[x][y];
int maxn=0;
maxn=dp(r[x],y);
for(int i=0;i<=y-1;i++){
int ls=y-i-1;int rs=i;
int lss=0,rss=0;
if(l[x]!=-1) lss=f[l[x]][ls]=dp(l[x],ls);
if(r[x]!=-1) rss=f[r[x]][rs]=dp(r[x],rs);
// if(ls<0) maxn=max(maxn,rss);
maxn=max(maxn,lss+rss+c[x]);
}
return maxn;
}
int main(){
// freopen("caioj1108.in","r",stdin);
scanf("%d%d",&n,&m);
memset(l,-1,sizeof l);memset(r,-1,sizeof r);
for(int i=1,x;i<=n;i++){
scanf("%d%d",&x,&c[i]);
ins(i,x);
}
memset(f,-1,sizeof f);
for(int i=0;i<=n;i++)f[i][0]=0; c[0]=0;
printf("%d\n",dp(0,m+1));
return 0;
}