Vijos1523 NOI2002 贪吃的九头龙 树形dp

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vijos1523 NOI2002 贪吃的九头龙 树形dp相关的知识,希望对你有一定的参考价值。

思路不算很难,但细节处理很麻烦

前面建图、多叉转二叉,以及确定dp处理序列的过程都是套路,dp的状态转移过程以注释的形式阐述

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <queue>
  5 
  6 int N,M,K;
  7 
  8 struct Edge
  9 {
 10     int to,next;
 11     int weight;
 12     void assign(int t,int n,int w)
 13     {
 14         to=t; next=n; weight=w;
 15     }
 16 };
 17 
 18 Edge elist[605];
 19 int head[305];
 20 bool vis[305];
 21 int child[305][2]; //0 is left and 1 is right
 22 int weight[305];
 23 int ecnt;
 24 
 25 void initE()
 26 {
 27     memset(head,-1,sizeof(head));
 28     memset(vis,0,sizeof(vis));
 29     memset(child,-1,sizeof(child));
 30     weight[1]=0;
 31     ecnt=0;
 32 }
 33 
 34 inline void addEdge(int from,int to,int weight)
 35 {
 36     elist[ecnt].assign(to,head[from],weight);
 37     head[from]=ecnt++;
 38     elist[ecnt].assign(from,head[to],weight);
 39     head[to]=ecnt++;
 40 }
 41 
 42 bool input()
 43 {
 44     scanf("%d%d%d",&N,&M,&K);
 45     bool ok=true;
 46     if(K+M-1>N) ok=false; //输出-1的情况
 47     initE();
 48     int a,b,c;
 49     for(int i=1;i<N;i++)
 50     {
 51         scanf("%d%d%d",&a,&b,&c);
 52         addEdge(a,b,c);
 53     }
 54     return ok;
 55 }
 56 
 57 void buildBinaryTree(int cur)
 58 {
 59     vis[cur]=true;
 60     int last=-1;
 61     for(int e=head[cur];e!=-1;e=elist[e].next)
 62     {
 63         int& to=elist[e].to;
 64         int& w=elist[e].weight;
 65         if(!vis[to])
 66         {
 67             weight[to]=w;
 68             if(last==-1)
 69                 child[cur][0]=to;
 70             else
 71                 child[last][1]=to;
 72             last=to;
 73             buildBinaryTree(to);
 74         }
 75     }
 76 }
 77 
 78 int dp[305][305][2];
 79 /*
 80 dp[n][m][k]:处理到第n个节点时,大头已经吃掉了m个果子,其父节点将被(k=1)或不被(k=0)大头吃,此情况下的最优值
 81 dp[n]的处理范围:孩子兄弟二叉树中,以节点n为根的子树
 82 dp的初值见下方代码,ans=dp[v][K-1][1],v为孩子兄弟二叉树中1号节点的左孩子
 83 */
 84 
 85 std::queue<int> seq;
 86 
 87 void getSeq(int cur)
 88 {
 89     if(child[cur][1]!=-1) getSeq(child[cur][1]);
 90     if(child[cur][0]!=-1) getSeq(child[cur][0]);
 91     seq.push(cur);
 92 } //先处理右孩子,再处理左孩子,最后处理自身
 93 
 94 /*
 95 M>=3时,小头的总难受值的最小值必为0
 96 取两个小头A和B,奇数层的果子由A吃,偶数层的果子由B吃,这样难受值必为0
 97 所以只需考虑大头的难受值
 98 */
 99 
100 int solve_aux_3() 
101 //若无特殊说明:solve_aux函数注释中提到的有关树的概念均指孩子兄弟二叉树,opt代指当前决策的较优值,“不吃”和“吃”均指大头
102 {
103     int ans;
104     while(!seq.empty())
105     {
106         int cur=seq.front();
107         seq.pop();
108         int st(0);
109         if(child[cur][1]!=-1) st|=1;
110         if(child[cur][0]!=-1) st|=2;
111         if(st==0) //cur是叶子节点
112         {
113             dp[cur][0][0]=dp[cur][0][1]=0;
114             dp[cur][1][0]=0;
115             dp[cur][1][1]=weight[cur];
116         }
117         else if(st==1) //只有右孩子,状态转移和线性dp类似
118         {
119             int& rc=child[cur][1];
120             dp[cur][0][0]=0;
121             for(int i=1;i<K;i++)
122                 dp[cur][i][0]=std::min(dp[rc][i][0],dp[rc][i-1][0]); //对于当前果子,opt=min(不吃,吃)
123             dp[cur][0][1]=0;
124             for(int i=1;i<K;i++)
125                 dp[cur][i][1]=std::min(dp[rc][i][1],dp[rc][i-1][1]+weight[cur]); //opt=min(不吃,吃)
126         }
127         else if(st==2) //只有左孩子
128         {
129             int& lc=child[cur][0];
130             if(cur==1) ans=dp[lc][K-1][1]; //最终答案
131             else
132             {
133                 dp[cur][0][0]=dp[cur][0][1]=0;
134                 for(int i=1;i<K;i++)
135                     dp[cur][i][0]=std::min(dp[lc][i][0],dp[lc][i-1][1]); //opt=min(不吃,吃)
136                 for(int i=1;i<K;i++)
137                     dp[cur][i][1]=std::min(dp[lc][i][0],dp[lc][i-1][1]+weight[cur]); //opt=min(不吃,吃)
138             }
139         }
140         else //st=3,既有左孩子又有右孩子,最复杂的情况
141         {
142             int& lc=child[cur][0];
143             int& rc=child[cur][1];
144             dp[cur][0][0]=dp[cur][0][1]=0;
145             for(int i=1;i<K;i++) //dp[cur][i][0]
146             {
147                 for(int j=0;j<=i;j++) //不吃当前的果子
148                     dp[cur][i][0]=std::min(dp[lc][j][0]+dp[rc][i-j][0],dp[cur][i][0]); //分配i,取最优的分配方案
149                 for(int j=0;j<i;j++) //吃当前的果子
150                     dp[cur][i][0]=std::min(dp[lc][j][1]+dp[rc][i-j-1][0],dp[cur][i][0]);
151             }
152             for(int i=1;i<K;i++) //dp[cur][i][1]
153             {
154                 for(int j=0;j<=i;j++) //不吃
155                     dp[cur][i][1]=std::min(dp[cur][i][1],dp[lc][j][0]+dp[rc][i-j][1]);
156                 for(int j=0;j<i;j++) //
157                     dp[cur][i][1]=std::min(dp[cur][i][1],dp[lc][j][1]+dp[rc][i-j-1][1]+weight[cur]);
158             }
159         }
160     }
161     return ans;
162 }
163 
164 /*
165 M=2时的dp方程与M>2时形式类似,但细节上有所不同(包括初值的设置和状态转移)
166 此时大头和小头的难受值必须同时考虑
167 */
168 
169 int solve_aux_2()
170 {
171     int ans;
172     while(!seq.empty())
173     {
174         int cur=seq.front();
175         seq.pop();
176         int st(0);
177         if(child[cur][1]!=-1) st|=1;
178         if(child[cur][0]!=-1) st|=2;
179         if(st==0)
180         {
181             dp[cur][0][1]=dp[cur][1][0]=0;
182             dp[cur][0][0]=dp[cur][1][1]=weight[cur]; //注意这里赋初值的差异
183             //dp[cur][0][0]表示原树中当前节点和父节点的果子都由小头吃,所以该段树枝的难受值也必须考虑
184             //类似的差异会在下方用“!”标注,请读者自行体会
185         }
186         else if(st==1)
187         {
188             int& rc=child[cur][1];
189             dp[cur][0][1]=dp[rc][0][1];
190             dp[cur][0][0]=dp[rc][0][0]+weight[cur]; //
191             for(int i=1;i<K;i++)
192             {
193                 dp[cur][i][0]=std::min(dp[rc][i][0]+weight[cur],dp[rc][i-1][0]); //
194                 dp[cur][i][1]=std::min(dp[rc][i][1],dp[rc][i-1][1]+weight[cur]);
195             }
196         }
197         else if(st==2)
198         {
199             int& lc=child[cur][0];
200             if(cur==1) ans=dp[lc][K-1][1];
201             else
202             {
203                 dp[cur][0][1]=dp[lc][0][0]; //
204                 dp[cur][0][0]=dp[lc][0][0]+weight[cur];
205                 for(int i=1;i<K;i++)
206                 {
207                     dp[cur][i][0]=std::min(dp[lc][i][0]+weight[cur],dp[lc][i-1][1]); //
208                     dp[cur][i][1]=std::min(dp[lc][i][0],dp[lc][i-1][1]+weight[cur]);
209                 }
210             }
211         }
212         else
213         {
214             int& lc=child[cur][0];
215             int& rc=child[cur][1];
216             dp[cur][0][1]=dp[lc][0][0]+dp[rc][0][1]; //
217             dp[cur][0][0]=dp[lc][0][0]+dp[rc][0][0]+weight[cur];
218             for(int i=1;i<K;i++)
219             {
220                 for(int j=0;j<=i;j++)
221                     dp[cur][i][0]=std::min(dp[cur][i][0],dp[lc][j][0]+dp[rc][i-j][0]+weight[cur]); //
222                 for(int j=0;j<i;j++)
223                     dp[cur][i][0]=std::min(dp[cur][i][0],dp[lc][j][1]+dp[rc][i-j-1][0]);
224                 for(int j=0;j<=i;j++)
225                     dp[cur][i][1]=std::min(dp[cur][i][1],dp[lc][j][0]+dp[rc][i-j][1]);
226                 for(int j=0;j<i;j++)
227                     dp[cur][i][1]=std::min(dp[cur][i][1],dp[lc][j][1]+dp[rc][i-j-1][1]+weight[cur]);
228             }
229         }
230     }
231     return ans;
232 }
233 
234 int solve()
235 {
236     memset(dp,0x3f,sizeof(dp));
237     buildBinaryTree(1);
238     getSeq(1);
239     return M==2?solve_aux_2():solve_aux_3();
240 }
241 
242 int main()
243 {
244     if(!input()) printf("-1");
245     else printf("%d",solve());
246     return 0;
247 }

 

以上是关于Vijos1523 NOI2002 贪吃的九头龙 树形dp的主要内容,如果未能解决你的问题,请参考以下文章

[codevs1746][NOI2002]贪吃的九头龙

NOI2002贪吃的九头龙

NOI2002 贪吃的九头龙

多叉树转二叉树+树形dp(codevs 1746 贪吃的九头龙 2002noi)

CODEVS 1746 贪吃的九头龙

codevs贪吃的九头龙