樹型動規 選課
知識點: 樹型DP、多叉樹轉二叉樹
多叉樹轉二叉樹表示方式之孩子兄弟鏈存儲結構:
孩子兄弟存儲結構是爲每個節點設計三個域:
- 一個數據元素域
- 一個該節點的第一個孩子節點指針域
- 一個該節點下的下一個兄弟節點指針域。
描述
學校實行學分制。每門的必修課都有固定的學分,同時還必須獲得相應的選修課程學分。學校開設了N(N<300)門的選修課程,每個學生可選課程的數量M是給定的。學生選修了這M門課並考覈通過就能獲得相應的學分。
在選修課程中,有些課程可以直接選修,有些課程需要一定的基礎知識,必須在選了其它的一些課程的基礎上才能選修。例如《Frontpage》必須在選修了《Windows操作基礎》之後才能選修。我們稱《Windows操作基礎》是《Frontpage》的先修課。每門課的直接先修課最多隻有一門。兩門課也可能存在相同的先修課。每門課都有一個課號,依次爲1,2,3,…。 例如:
表中1是2的先修課,2是3、4的先修課。如果要選3,那麼1和2都一定已被選修過。 你的任務是爲自己確定一個選課方案,使得你能得到的學分最多,並且必須滿足先修課優先的原則。假定課程之間不存在時間上的衝突。
格式
輸入格式
輸入文件的第一行包括兩個整數N、M(中間用一個空格隔開)其中1≤N≤300,1≤M≤N。
以下N行每行代表一門課。課號依次爲1,2,…,N。每行有兩個數(用一個空格隔開),第一個數爲這門課先修課的課號(若不存在先修課則該項爲0),第二個數爲這門課的學分。學分是不超過10的正整數。
輸出格式
輸出文件每行只有一個數。第一行是實際所選課程的學分總數。
樣例1
樣例輸入
5 4
0 1
1 1
2 3
0 3
2 4
樣例輸出
9
樣例2
樣例輸入
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
樣例輸出
13
/*
一道樹形dp,因爲每門課的先修課最多隻有一節,則說明這是個樹結構
對於讀入的邊,我們先將多叉樹建立成二叉樹
原則是左兒子右兄弟
然後再定義狀態f表示以i課程爲父親結點的子樹上選j門課程的最多學分
則有狀態轉移方程
f[i][j]=max(f[left][j-k-1]+f[right][k]+score[i](0<=k<=j-1),f[right][j])
方程含義:1、取當前i節點,則剩下的j-1們課程,在孩子中選j-k-1門,在兄弟中選k門。
2、不取當前節點,則只能在兄弟中選j門。
k用來表示的是分別分配在某個節點上的左右子樹的選擇課程樹
注意還有個f[right][j]即表示不選這門課程而選擇它的右兄弟
原理上兩種情況是根本上相同可合併的但是實際實現中沒有那麼方便
所以我們可以自頂向下用dfs進行樹的遍歷遞歸求解
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=305;
struct node
{
int l,r,v;
}tree[maxn];
int n,m;
int f[maxn][maxn];
int dfs(int x,int y)
{
if(!y||x<0)
return 0;
if(!x)
return dfs(tree[x].l,y);
if(f[x][y])
return f[x][y];
f[x][y]=dfs(tree[x].r,y);
for(int i=0;i<y;i++)
f[x][y]=max(f[x][y],dfs(tree[x].l,i)+tree[x].v+dfs(tree[x].r,y-i-1));
return f[x][y];
}
int main()
{
memset(tree,-1,sizeof(tree));
cin>>n>>m;
int fa,v;
for(int i=1;i<=n;i++)
{
cin>>fa>>v;
tree[i].r=tree[fa].l;
tree[fa].l=i;
tree[i].v=v;
}
cout<<dfs(0,m)<<endl;//0表示入口,0的子節點表示第一層
return 0;
}