贪吃的九头龙 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;
}
如有不明之处,敬请提出!!!