1. 程式人生 > >LG-P2014 選課

LG-P2014 選課

P2014 選課 題目連結 題目描述 在大學裡每個學生,為了達到一定的學分,必須從很多課程裡選擇一些課程來學習,在課程裡有些課程必須在某些課程之前學習,如高等數學總是在其它課程之前學習。現在有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門課程的最大得分。

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

題解 樹形依賴揹包。 這類問題,往往是子節點需要在選擇父節點後才能選擇。

這題就是典型。這種DP是可以做到 O(NM)O(NM),雖然這裡的 n 和 m,只有 300。

優化方案:利用 dfs 序,我們會發現每次只需要從上一個遍歷到的點,和上一棵子樹轉移過來就好了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5005,maxm=maxn;
int n,m,
tot,lnk[maxn],son[maxm],nxt[maxm],f[maxn][maxn],a[maxn],s[maxn],dfn[maxn],id[maxn],ti; void add(int x,int y) { son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot; } int rad() { int ret=0,f=1;char ch=getchar(); while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9'
) ret=ret*10+ch-'0',ch=getchar(); return ret*f; } void dfs(int x) { s[id[dfn[x]=++ti]=x]=1; for (int i=lnk[x],y;i;i=nxt[i]) { if (dfn[y=son[i]]) continue; dfs(y);s[x]+=s[y]; } } int main() { freopen("class.in","r",stdin); freopen("class.out","w",stdout); n=rad();m=rad();ti=0;tot=0; memset(lnk,0,sizeof lnk); memset(f,0,sizeof f); for (int i=1,x;i<=n;++i) x=rad(),a[i]=rad(),add(x,i); ++n;++m; if (m>n) m=n; dfs(0); for (int i=n;i>=1;--i) for (int j=1;j<=m;++j) f[i][j]=max(f[i+1][j-1]+a[id[i]],f[i+s[id[i]]][j]); printf("%d\n",f[1][m]); return 0; }