首页 > 代码库 > NOI2002 贪吃的九头龙
NOI2002 贪吃的九头龙
【问题描述】
传说中的九头龙是一种特别贪吃的动物。虽然名字叫“九头龙”,但这只是说它出生的时候有九个头,而在成长的过程中,它有时会长出很多的新头,头的总数会远大于九,当然也会有旧头因衰老而自己脱落。
有一天,有M个脑袋的九头龙看到一棵长有N个果子的果树,喜出望外,恨不得一口把它全部吃掉。可是必须照顾到每个头,因此它需要把N个果子分成M组,每组至少有一个果子,让每个头吃一组。
这M个脑袋中有一个最大,称为“大头”,是众头之首,它要吃掉恰好K个果子,而且K个果子中理所当然地应该包括唯一的一个最大的果子。果子由N-1根树枝连接起来,由于果树是一个整体,因此可以从任意一个果子出发沿着树枝“走到”任何一个其他的果子。
对于每段树枝,如果它所连接的两个果子需要由不同的头来吃掉,那么两个头会共同把树枝弄断而把果子分开;如果这两个果子是由同一个头来吃 掉,那么这个头会懒得把它弄断而直接把果子连同树枝一起吃掉。当然,吃树枝并不是很舒服的,因此每段树枝都有一个吃下去的“难受值”,而九头龙的难受值就 是所有头吃掉的树枝的“难受值”之和。
九头龙希望它的“难受值”尽量小,你能帮它算算吗?
例如图1所示的例子中,果树包含8个果子,7段树枝,各段树枝的“难受值”标记在了树枝的旁边。九头龙有两个脑袋,大头需要吃掉4个果子,其中必须包含最大的果子。即N=8,M=2,K=4:
大头吃4个果子,用实心点标识;
小头吃4个果子,用空心点标识;
九头龙的难受值为4,因为图中用细边标记的树枝被大头吃掉了。
图一描述了果树的形态,图二描述了最优策略。
【输入文件】
输入文件的第1行包含三个整数N (1<=N<=300),M (2<=M<=N),K (1<=K<=N)。 N个果子依次编号1,2,...,N,且最大的果子的编号总是1。第2行到第N行描述了果树的形态,每行包含三个整数a (1<=a<=N),b (1<=b<=N),c (0<=c<=105),表示存在一段难受值为c的树枝连接果子a和果子b。
【输出文件】
输出文件仅有一行,包含一个整数,表示在满足“大头”的要求的前提下,九头龙的难受值的最小值。如果无法满足要求,输出-1。
【样例输入】
8 2 4 1 2 20 1 3 4 1 4 13 2 5 10 2 6 12 3 7 15 3 8 5
【样例输出】
4
时间限制:1 s 内存限制:128 MB
题目大意:有一只M个头的九头龙,想吃掉有N个果实的树,要求每个小头至少吃1个果实,大头吃恰好K个果实,且树根上的果实也需由大头吃。
对于一个果实,如果同一个头同时吃掉这个果实以及这个果实的父亲,就会受到一定的难受值,现在求吃掉这棵树的最小难受值。
首先判断无解,很明显仅当N<M+K-1时会无解。
题目中头的数目2<=M<=N咋一看可能会被吓到,感觉无从下手,但仔细想一想M个脑袋是可以简化掉的,大致有两种情况:
①M=2时 这时候大头和小头的难受值都需要考虑,但因为只有两个头还是可以解决的。
②M>2时 这时候只需要考虑大头的难受值,因为小头完全可以避开吃掉树枝。
然后为了方便转移,我们把可以把这棵多叉树转换成二叉树,这样问题就简单多了。
到了这里这道题目就变成一道简单的树形DP了
f[i][j][1/0] 表示以i为根的子树中,大头要吃j个果实,且其父结点也被大头吃了(1)或没被大头吃(0),的最小难受值
转移方程详见程序,这里说个要注意的地方:对于一棵大头不吃任何果实的树,若M>2可以直接得出难受值为0,但是若M==2便还是
要遍历到叶结点才结束。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define R register 5 6 struct info{ 7 int to,pre,dis; 8 }edge[1007]; 9 10 bool pd[307][307]; 11 int n,m,k,size,last[307],head[307]; 12 int f[307][307][2],fa[307],l[307],r[307],up[307]; 13 14 void addline(int from,int to,int dis){ 15 edge[++size].pre=head[from];head[from]=size; 16 edge[size].to=to;edge[size].dis=dis; 17 edge[++size].pre=head[to];head[to]=size; 18 edge[size].to=from;edge[size].dis=dis; 19 } 20 21 void tsdp(int k,int num){ 22 if (pd[k][num]) return; 23 f[k][num][1]=f[k][num][0]=0x7ffffff; 24 if (!k||!num){ //处理0结点和大头不吃果实的情况 25 if (!num&&(m!=2||!k)) f[k][num][0]=f[k][num][1]=0; 26 if (m!=2||!k)return; 27 } 28 if (!l[k]&&!r[k]){ //处理叶结点 29 if (num==1) f[k][1][1]=up[k],f[k][1][0]=0; 30 if (!num) f[k][0][0]=up[k],f[k][0][1]=0; 31 } 32 for (R int i=0;i<=num;++i){ //状态转移 33 tsdp(l[k],i); 34 tsdp(r[k],num-i); 35 f[k][num][1]=std::min(f[k][num][1],f[l[k]][i][0]+f[r[k]][num-i][1]); 36 if (i) 37 f[k][num][1]=std::min(f[k][num][1],f[l[k]][i-1][1]+up[k]+f[r[k]][num-i][1]); 38 if (i) 39 f[k][num][0]=std::min(f[k][num][0],f[l[k]][i-1][1]+f[r[k]][num-i][0]); 40 if (m==2) 41 f[k][num][0]=std::min(f[k][num][0],f[l[k]][i][0]+up[k]+f[r[k]][num-i][0]); 42 else 43 f[k][num][0]=std::min(f[k][num][0],f[l[k]][i][0]+f[r[k]][num-i][0]); 44 } 45 pd[k][num]=true; 46 } 47 48 void DFS(int k){ 49 for (R int i=head[k];i;i=edge[i].pre) 50 if (edge[i].to!=fa[k]){ 51 fa[edge[i].to]=k; 52 DFS(edge[i].to); 53 up[edge[i].to]=edge[i].dis; 54 } 55 } 56 57 int main(){ 58 scanf("%d%d%d",&n,&m,&k); 59 for (R int i=1;i<n;++i){ 60 int a,b,c; 61 scanf("%d%d%d",&a,&b,&c); 62 addline(a,b,c); 63 } 64 DFS(1); 65 for (R int i=2;i<=n;++i){ //多叉转二叉 66 if (last[fa[i]]) r[last[fa[i]]]=i; 67 else l[fa[i]]=i; 68 last[fa[i]]=i; 69 } 70 tsdp(1,k); 71 if (n<k+m-1) printf("-1"); 72 else printf("%d",f[l[1]][k-1][1]); 73 }
PS:这个程序写得太丑了,又臭又长,但又不想重写,凑合着看一下吧TAT
评测地址:https://www.vijos.org/p/1523
NOI2002 贪吃的九头龙