题解 P4362 【[NOI2002]贪吃的九头龙】
这道题不难看出是一道树形DP的题目,但我在写题时突发奇想,如有不对,各位请指正。
我们在读题时可以发现这样一个问题,数据范围特别水。。。N <= 300而已。其实这样的大小可以说是DP的潜台词了,但作为一个从不按套路出牌的我,决定利用前缀和水过去。
我们将样例画出来后会发现可以将整棵树分成四部分:1,以最大的果实为根节点的自己;2,以最左边的二代子节点为根节点的第一子树;3,以中间的二代子节点为根节点的第二子树;4,以最右边的二代子节点为根节点的第三子树。
这样分开有些问题就一目了然了
问题瞬间简化为选择题,我们只需要考虑取min{父亲,第一子树和,第二子树和,第三子树和},除非选择父亲节点,不然继续递归。
解释完样例后,我们可以将这个规律发扬。
发扬规律不难理解,但我们还面临一个问题就是大头吃K个果子,一旦K小于子树个数就有些麻烦。
为解决问题我们可以自己写一个min函数,原理可以参考快排,在这里不多加赘述。
不过尴尬的是虽然我说了这么多,但我自己写的代码还是树形DP的(毕竟这是突发奇想嘛,大家理解一下),这个想法的实现就留给读者自己思考了(好熟悉的话)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int _=305;
inline int read()
{
char ch = '!';
int z = 1 , num = 0;
while(ch != '-' && (ch < '0' || ch > '9'))
ch = getchar();
if(ch == '-')
{
z = -1;
ch = getchar();
}
while(ch <= '9' && ch >= '0')
{
num = (num << 3) + (num << 1) + ch - '0';
ch = getchar();
}
return z * num;
}
int N , M , K , f[_][_][2] , tmp[_][2];
struct hand
{
int to , next , w;
}e[_<<1];
int cnt , head[_];
void link(int u , int v , int w)
{
e[++cnt] = (hand){v,head[u],w};
head[u]=cnt;
}
void dfs(int u,int fa)
{
f[u][0][0]=f[u][1][1]=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa)continue;
dfs(v,u);
memcpy(tmp,f[u],sizeof(tmp));
memset(f[u],63,sizeof(f[u]));
for(int j=0;j<=K;++j)
{
for(int t=0;t<=j;++t)
{
f[u][j][0]=min(f[u][j][0],min(f[v][t][0]+tmp[j-t][0]+(M==2)*e[i].w,f[v][t][1]+tmp[j-t][0]));
f[u][j][1]=min(f[u][j][1],min(f[v][t][1]+tmp[j-t][1]+e[i].w,f[v][t][0]+tmp[j-t][1]));
}
}
}
}
int main()
{
N=read();M=read();K=read();
if(N-K<M-1){puts("-1");return 0;}
for(int i=1;i<N;++i)
{
int x=read(),y=read(),z=read();
link(x,y,z);link(y,x,z);
}
memset(f,63,sizeof(f));
dfs(1,1);
printf("%d\n",f[1][K][1]);
return 0;
}
有什么意见或者让我打脸的证明欢迎私信我(打我脸的仁兄轻一点)