首页 > 代码库 > 树形DP-----HDU4003 Find Metal Mineral
树形DP-----HDU4003 Find Metal Mineral
Find Metal Mineral
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Others) Total Submission(s): 2371 Accepted Submission(s): 1079
3 1 1 1 2 1 1 3 1 3 1 2 1 2 1 1 3 1
dp[pos][num]表示以pos为根节点的子树下,用去num个机器人,所得到的最小值
特别的是当num==0的时候,dp[pos][0]表示用一个机器人去走完所有子树,最后又回到pos这个节点
状态转移:dp[pos][num]=min∑{dp[pos_j][num_j]+w_j},pos_j是pos的所有儿子,
当num_j==0的时候,和别的时候不同,特别处理一下就好。
状态转移并不难,最精华的,我不认为是状态转移,而是转移时使用的那个“分组背包”思想。
使用一维数组的“分组背包”伪代码如下:
for 所有的组i
for v=V..0
for 所有的k属于组i
f[v]=max{f[v],f[v-c[k]]+w[k]}
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 using namespace std; 6 7 struct Edge{ 8 int to; 9 int value;10 int next;11 }edge[2*10008];12 int head[10008],dp[10008][12];13 int N,S,K,total;14 15 inline int MIN(int a,int b)16 {17 if(a<b) return a;18 return b;19 }20 void addEdge(int start,int end,int value)21 {22 edge[total].to=end;edge[total].value=http://www.mamicode.com/value;23 edge[total].next=head[start];head[start]=total++;24 25 edge[total].to=start,edge[total].value=http://www.mamicode.com/value;26 edge[total].next=head[end],head[end]=total++;27 }28 29 void DP(int source,int pre)30 {31 for(int i=head[source];i!=-1;i=edge[i].next)32 {33 int to=edge[i].to;34 if(to==pre) continue;35 DP(to,source);36 for(int j=K;j>=0;j--)37 {38 dp[source][j]+=dp[to][0]+2*edge[i].value;39 for(int k=1;k<=j;k++)40 dp[source][j]=MIN(dp[source][j],dp[source][j-k]+dp[to][k]+k*edge[i].value);41 }42 }43 }44 45 int main()46 {47 while(scanf("%d %d %d",&N,&S,&K)!=EOF)48 {49 memset(dp,0,sizeof(dp));50 memset(head,-1,sizeof(head));51 total=0;52 53 int x,y,cost;54 for(int i=1;i<N;i++)55 {56 scanf("%d %d %d",&x,&y,&cost);57 addEdge(x,y,cost);58 }59 DP(S,-1);60 printf("%d\n",dp[S][K]);61 }62 return 0;63 }
思路和其他人差不多,谈谈自己是怎么理解的吧:
首先,用两数组建立无向树,还是第一次,费了不少功夫。定义一个结构代表边edge[i]={to,next,value},其中edge[i]表示第i条边,to是边的末点,value就不说了,next代表的是同一父节点边方向相同的与此边相邻的左边的边的编号,其中最左是-1。以此为基础建立一个边数组(注意数组大小是顶点数的两倍),还有用一个head[i]数组来表示最右边的边的编号。
解释这一段代码,当时写的理解的不是很好:
1 void DP(int source,int pre) 2 { 3 for(int i=head[source];i!=-1;i=edge[i].next) 4 { 5 int to=edge[i].to; 6 if(to==pre) continue; 7 DP(to,source); 8 for(int j=K;j>=0;j--) 9 {10 dp[source][j]+=dp[to][0]+2*edge[i].value;11 for(int k=1;k<=j;k++)12 dp[source][j]=MIN(dp[source][j],dp[source][j-k]+dp[to][k]+k*edge[i].value);13 }14 }15 }
第一层for循环查找与节点source相连的各条边,to是此边对应的末点,若此边是叶子节点则to==pre(由于节点最左边的边访问完以后,只有到他父节点的边,这里只能用continue,因为不确定访问的顺序,return不合适),递归访问子节点此时应用01背包,由于dp[source][0]比较特殊单独处理,一棵子树对应一个分组,dp[source][j]=MIN(dp[source][j],dp[source][j-k]+dp[to][k]+k*edge[i].value);也比较好理解。