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的主要内容,如果未能解决你的问题,请参考以下文章