1. 程式人生 > >JSOI 2008 【魔獸地圖】

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;
}