首页 > 代码库 > 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 贪吃的九头龙