JSOI 2008 【魔獸地圖】
其實這題是我從noip前就開始做的。。。那個時候打的Pascal,一直TLE,轉了C++之後我又寫了一遍,A了。。。
辛酸史:
題目描述:
DotR (Defense of the Robots) Allstars是一個風靡全球的魔獸地圖,他的規則簡單與同樣流行的地圖DotA (Defense of the Ancients) Allstars。
DotR裡面的英雄只有一個屬性——力量。他們需要購買裝備來提升自己的力量值,每件裝備都可以使佩戴它的英雄的力量值提高固定的點數,所以英雄的力量值等於它購買的所有裝備的力量值之和。裝備分為基本裝備和高階裝備兩種。基本裝備可以直接從商店裡面用金幣購買,而高階裝備需要用基本裝備或者較低階的高階裝備來合成,合成不需要附加的金幣。裝備的合成路線可以用一棵樹來表示。
比如,Sange and Yasha的合成需要Sange,Yasha和Sange and Yasha Recipe Scroll三樣物品。其中Sange又要用Ogre Axe, Belt of Giant Strength和 Sange Recipe Scroll合成。每件基本裝備都有數量限制,這限制了你不能無限制地合成某些價效比很高的裝備。
現在,英雄Spectre有M個金幣,他想用這些錢購買裝備使自己的力量值儘量高。你能幫幫他嗎?他會教你魔法Haunt(幽靈附體)作為回報的。
資料範圍:物品數(n)<=51,金幣數(m)<=2000,限購數量(limit)<=100。
思路分析:
可以說是一道樹上揹包DP吧。
觀察題目,注意到:限購數量是題目的關鍵,對於一個物品,它合成幾件,或是把幾件用於父親裝備的合成,是題目噁心的地方,既然這樣的話我們就把它設計到狀態裡面。然後再看看資料範圍,限購小於等於100——很明顯了,出題人就是想讓我們把限購數量放進狀態裡面。再和普通的揹包結合一下,得到狀態,f[u][i][j]表示:對於以u為根的子樹,在以u為根的子樹上,我們投入i個金幣,並且把j個物品u用於父親裝備的合成,以u為根的子樹能產生的最大力量值。
然後我們還需要一個g陣列來輔助f陣列的轉移。g[i][j]表示:對於u的前i個兒子裝備,在以它們為根的子樹中投入j個金幣,能得到的最大力量值。
轉移時我們需要列舉的很重要的一環就是:製作幾個u裝備,從而讓整個轉移能夠順利地進行下去,我們把它設為l。
得到g陣列的轉移:g[i][j]=max(g[i][j],g[i-1][j-k]+f[v][k][l*need]) 其中v為u的第i個兒子,而need則為製作一個裝備u需要幾個裝備v,k為在以v為根的子樹中花費k個金幣。
通過g陣列的轉移,繼而得到f陣列的轉移:f[u][i][j]=max(f[u][i][j],g[siz[u]][i]+w[u]*(l-j)) 其中siz[u]表示u有幾個兒子。
最後求答案的時候我們又需要一個dp陣列來求解。(嗯,沒錯,一道題目,三個揹包dp,我也不得不佩服出題人了——你是真TM的毒瘤!)
最後這個就是最簡單的揹包dp了(如果這個都想不到的話,建議你前往xx省xx市實驗學校,和某張姓老師學習一段時間)
狀態dp[i][j],表示前i個“終極裝備”(終極裝備就是無法用於合成的裝備),花費j個金幣,能得到的最大力量值。轉移的時候列舉一下在第i個裝備上花費多少金幣就可以了,具體不懂的話還是看程式碼吧,太簡單了,也不想多講了。
程式碼實現:
#include<bits/stdc++.h> using namespace std; const int inf=1000000000; int f[55][2005][105],g[55][2005],dp[55][2005]; int a[55][55],need[55][55],limit[55],cost[55],w[55],siz[55]; bool root[55],son[55]; int n,m; void dfs(int u){ if (!son[u]){ limit[u]=min(limit[u],m/cost[u]); for (int i=0;i<=limit[u];i++) for (int j=0;j<=i;j++) f[u][i*cost[u]][j]=w[u]*(i-j); return; } limit[u]=inf; for (int i=1;i<=siz[u];i++){ dfs(a[u][i]); cost[u]+=cost[a[u][i]]*need[u][i]; limit[u]=min(limit[u],limit[a[u][i]]/need[u][i]); } limit[u]=min(limit[u],m/cost[u]); memset(g,-0x3f,sizeof(g)); g[0][0]=0; for (int l=limit[u];l>=0;l--){ //這裡倒著列舉l,是為了避免多次給g陣列賦初值 for (int i=1;i<=siz[u];i++) for (int j=0;j<=m;j++) for (int k=0;k<=j;k++) g[i][j]=max(g[i][j],g[i-1][j-k]+f[a[u][i]][k][l*need[u][i]]); for (int i=0;i<=l;i++) for (int j=0;j<=m;j++) f[u][j][i]=max(f[u][j][i],g[siz[u]][j]+w[u]*(l-i)); } } int main(){ memset(f,-0x3f,sizeof(f)); scanf("%d%d",&n,&m); int nn,ans=0; for (int i=1;i<=n;i++){ scanf("%d",&w[i]); char ch[3]; scanf("%s",&ch); if (ch[0]=='A'){ scanf("%d",&nn); for (int j=1;j<=nn;j++) scanf("%d%d",&a[i][j],&need[i][j]),root[a[i][j]]=true; son[i]=true; siz[i]=nn; } else scanf("%d%d",&cost[i],&limit[i]); } int now=0; for (int i=1;i<=n;i++) if (!root[i]){ dfs(i); now++; for (int j=0;j<=m;j++) for (int k=0;k<=j;k++) dp[now][j]=max(dp[now][j],dp[now-1][j-k]+f[i][k][0]); } for (int i=0;i<=m;i++) ans=max(ans,dp[now][i]); printf("%d\n",ans); return 0; }