1. 程式人生 > >森林轉二叉樹(Vijos1180 選課)

森林轉二叉樹(Vijos1180 選課)

學校實行學分制。每門的必修課都有固定的學分,同時還必須獲得相應的選修課程學分。學校開設了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

樣例輸入1
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
樣例輸出1
13

這道題是不是比較噁心?
這和我們之前做的題不同,他這裡不是二叉樹,而是多叉樹。
而且….
可能A節點有5個兒子,而B節點只有1個兒子..
這麼噁心的東東,就要有噁心的演算法來對付他
在發程式碼之前,我們先說一個森林轉二叉樹的東東

森林裡面有什麼?樹啊
這麼多的樹我們要把它轉成一棵二叉樹?
這裡寫圖片描述

不急,我們先將將一棵樹轉為一棵二叉樹

這是一棵樹:
這裡寫圖片描述
對你沒看錯這是一棵可(惡)愛(心)的樹樹

這裡寫圖片描述
我們給同級的兄弟們加上了線
讓他們成為了一個整體

這裡寫圖片描述
我們給除了跟第一個左孩子
和與你同級的第一個左孩子
連線
剩下的線全部去掉!!!
嗯這些線賊噁心我們去掉去掉

這裡寫圖片描述

整理一波 二叉樹耶!

至於森林轉二叉樹

這裡寫圖片描述
首先這是一片森林(是的我沒逗你)

這裡寫圖片描述
然後我們按剛剛的方法修建一下這些樹樹
變成幾棵二叉樹

這裡寫圖片描述
以第一棵樹為根節點
每一棵轉化而來的二叉樹都作為前一棵樹的右孩子
然後
神奇的事情發生了
二叉樹!!

然而剛才講這麼多
好像
好像對於這道題並沒有什麼卵用得
是的我們只需要用到第一步就夠了
程式碼:

#include <cstdio>
#include <cstring>
using namespace std;
struct node
{
    int lc,rc,c,v;
    //lc(左兒子):他真正的兒子
    //rc(右兒子):他的兄弟
    node()
    {
        lc=rc=-1;c=0;v=0;
    }
}a[11000];
int f[1100][1100];
int mymax(int x,int y) 
{
    return x>y?x:y;
}
int treedp(int x,int y)
{
    if (x<0||y<0) return 0;
    if (f[x][y]!=-1) return f[x][y];
    int maxx=0;
    for (int i=0;i<=y;i++)//列舉
    {
        int ls=y-i-1,rs=i;
        //分成左右兩部分,左邊ls分,右邊rs分
        int lss=0,rss=0;
        if (a[x].lc!=-1) lss=f[a[x].lc][ls]=treedp(a[x].lc,ls);
        if (a[x].rc!=-1) rss=f[a[x].rc][rs]=treedp(a[x].rc,rs);
        if (ls<0) maxx=mymax(maxx,rss);
        else maxx=mymax(maxx,lss+rss+a[x].c);
    }
    return maxx;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        int x;
        scanf("%d%d",&x,&a[i].c);
        //講這麼多其實寫出來就一個if語句
        if (a[x].v==0) a[x].lc=i;//如果他沒有兒子就把它收為他的左兒子
        else a[a[x].v].rc=i;//否則就把它作為他兒子的兄弟(就是他左兒子的右兒子)
        a[x].v=i;
    }
    memset(f,-1,sizeof(f));
    for (int i=1;i<=n;i++)
        f[i][0]=0;
    printf("%d\n",treedp(0,m+1));
    return 0;
}