贪吃的九头龙

贪吃的九头龙 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;
}

如有不明之处,敬请提出!!!

猜你喜欢

转载自blog.csdn.net/G20202502/article/details/79587156
今日推荐