貪喫的九頭龍 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;
}
如有不明之處,敬請提出!!!