【樹形DP】貪喫的九頭龍

貪喫的九頭龍 NOI2002

題意

這裏寫圖片描述

思路

首先,判斷是否有解是十分簡單的。我們只需要看在給每個小頭分配1個,大頭分配K個的情況下,所需要的果子的數量是否大於了蘋果的總數。也就是M+K是否>N。
接下來就是有解的情況了。
首先我們需要知道,再分配好大頭之後,剩下的果子必然存在一種分配方式,使得九頭龍的難受值不會再增加。我們可以先考慮一堆連續的有連邊的果子,我們爲了減小難受值,應該儘量分配不同的頭來喫。由於所有的小頭需求的果子數量是沒有限制的,那麼我們就可以極限地想,在這一堆果子中分配完全不同的小頭來喫;若這一堆裏邊的果子數量大於了小頭數,那麼又可以循環地進行類似的操作。
解決了這個問題之後,我們就只需要考慮大頭了。對於每一個果子來說,它要麼是被大頭喫,要麼是不被。於是,我們便可以用樹形DP來解決:
我們定義狀態:

DP[u][sum][0]表示以u爲根節點,它以及它的兒子中,大頭吃了sum個果子,並且當前這個沒有被大頭喫。
那麼DP[u][sum][1]就是表示以u爲根節點,它以及它的兒子中,大頭吃了sum個果子,並且當前這個被大頭吃了。

那麼我們就可以利用樹上揹包來進行K的分配從而求得最優解了。
DP[u][sum][0]=sigma{min(DP[v][j][0],DP[v][j][1])} sigma{j}=sum;
DP[u][sum][1]=sigma{min(DP[v][j][0],DP[v][j][1]+len)} sigma{j}=sum-1;
這裏要注意的是,如果M==2,那麼之前我們定義的那一堆果子中就只能放同一種果子,這就意味着之前的結論已經不成立了,也就是給小頭分配果子也需要增加難受值。DP式也要發生變化:
DP[u][sum][0]=sigma{min(DP[v][j][0]+len,DP[v][j][1])} sigma{j}=sum;
DP[u][sum][1]=sigma{min(DP[v][j][0],DP[v][j][1]+len)} sigma{j}=sum-1;
最終狀態也就是DP[1][K][1].

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 300
#define INF 0x3FFFFFFF
using namespace std;
struct edge
{
    int len,to;
    edge(){}
    edge(int _len,int _to):len(_len),to(_to){}
};
struct point
{
    int ch[2];
}poi[MAXN+5];
vector<edge> G[MAXN+5];
int dp[MAXN+5][MAXN+5][2];
int N,M,K;
void dfs1(int u,int fa)
{
    dp[u][0][0]=dp[u][1][1]=0;
    //以當前點爲所有大頭選的果子的最後一個,或者是根本不選它以及它以後的果子
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i].to;
        if(v!=fa)
        {
            dfs1(v,u);
            for(int sum=K;sum>=0;sum--)
            //由於要防止之前的狀態重複被計算進去(由0-1揹包可知),我們需要倒着枚舉sum值
            {
                int ret=INF;
                for(int j=0;j<=sum;j++)
                {
                    ret=min(ret,min(dp[v][j][0]+G[u][i].len,dp[v][j][1])+dp[u][sum-j][0]);
                }
                dp[u][sum][0]=ret;
                ret=INF;
                for(int j=0;j<=sum;j++)
                {
                    ret=min(ret,min(dp[v][j][0],dp[v][j][1]+G[u][i].len)+dp[u][sum-j][1]);
                    //由於這裏的dp[u][sum-j][1]已經包括了根節點u,那麼之前的狀態是dp[u][sum-j][1]而不是dp[u][sum-j-1][1]
                }
                dp[u][sum][1]=ret;
            }
        }
    }
}
void dfs2(int u,int fa)
{
    dp[u][0][0]=dp[u][1][1]=0;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i].to;
        if(v!=fa)
        {
            dfs2(v,u);
            for(int sum=K;sum>=0;sum--)
            {
                int ret=INF;
                for(int j=0;j<=sum;j++)
                {
                    ret=min(ret,min(dp[v][j][0],dp[v][j][1])+dp[u][sum-j][0]);
                }
                dp[u][sum][0]=ret;
                ret=INF;
                for(int j=0;j<=sum;j++)
                {
                    ret=min(ret,min(dp[v][j][0],dp[v][j][1]+G[u][i].len)+dp[u][sum-j][1]);
                }
                dp[u][sum][1]=ret;
            }
        }
    }
}
void Transform()
{
    for(int i=0;i<=MAXN;i++)
        for(int j=0;j<=MAXN;j++)
            for(int k=0;k<=1;k++)
                dp[i][j][k]=INF;//由於是要求解最小值,我們便將所有的值初始化爲INF
}
int main()
{
    scanf("%d %d %d",&N,&M,&K);
    int sum=0;
    for(int i=1;i<=N-1;i++)
    {
        int len,u,v;
        scanf("%d %d %d",&u,&v,&len);
        sum+=len;
        G[u].push_back(edge(len,v));
        G[v].push_back(edge(len,u));
    }
    if(M+K>N)//無法滿足
    {
        printf("-1\n");
        return 0;
    }
    Transform();
    if(M==1)//全部給大頭
    {
        if(K==N)
            printf("%d\n",sum);
        else
            printf("-1\n");
        return 0;
    }
    if(M==2)//只有兩個頭(一個小頭)
        dfs1(1,-1);
    else//有多個頭
        dfs2(1,-1);
    printf("%d\n",dp[1][K][1]);//末狀態
    return 0;
}

如有不明之處,敬請提出!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章