这两天在做pku3345,是一道关于树形dp的题目,和动态的背包问题很相似,不过是对树进行dp,关键是找到dp方程。通过这道题目复习了下动态规划(dynamic programming)
动态规划一般是通过划分问题得到原问题的一系列子问题,通过求和子问题的最优解最后得到原问题的最优解。
如背包问题,对于最优解{y1,y2,y3...yn}取出任意一个值yk,剩下的解依旧是原问题取出yk后的最优解,即具有最优子结构性质。只要求的原问题的子问题的最优解并和并子问题的最优解即可得到原问题的最优解。
对于背包问题,可以设m[i][j]为从物品i-n中选出不操作重量j的物品的最大价值,递归方程为:
m[i][j] = max{m[i+1][j],m[i+1][j-wi]+vi} j>=wi
m[i][j] = m[i+1][j] 0<=j< wi
m[n][j] = vn j >=wn
m[n][j] = 0 0<=j<wn
在写代码是需要进行降序计算,即从n-->1计算m[i][j],原因在于m[i][j]的值依赖于m[i+1][j],所以需先求出m[i+1][j]再求m[i][j]。
关于pku3345这道题目,同样使用的背包问题方法,但是求解的对象是树形的,要在一棵树种选出至少m个节点使得花费最小。动态方程为:
dp[i][j]表示从以i为根的节点下面选择j个节点所花费的最小代价。
dp[i][j] = min{dp[i][j], dp[i][j-k] +dp[son][k]}其中son表示节点i的所有子节点(需要遍历),k表示从以i为根的节点下面选择j个节点中的k个节点从i的子孩子中选择。(k的选择范围是从0-子节点son所控制的所有节点数量)
这边代码中依旧是先计算dp[i][j]在计算dp[i][j-k]原因在于每个子节点都相互独立的,如果先计算dp[i][j-k]则会和其他子节点的结果产生关联。
可能写的太乱了,直接贴代码吧
#include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX 1<<20 #define MAX_COUNTRY_NAME 101 #define MAX_COUNTRY_NUM 201 #define MAX_CHILD_COUNT MAX_COUNTRY_NUM struct node { int count; int cost; int visited; int dominatedCountryNum; int isChild; int children[MAX_CHILD_COUNT]; }; node tree[MAX_COUNTRY_NUM]; int dp[MAX_COUNTRY_NUM][MAX_COUNTRY_NUM]; char countries[MAX_COUNTRY_NUM+1][MAX_COUNTRY_NAME]; int findCountry(char * name) { int i = 1; for(; i< MAX_COUNTRY_NUM;++i) { if(countries[i][0] == 0)break; if(strcmp(name,countries[i]) == 0)return i; } strcpy(countries[i],name); return i; } void init(int n) { memset(tree,0,sizeof(tree)); memset(dp,0x7f,sizeof(dp)); memset(countries,0,sizeof(countries)); } int calcuDonimatedNum(int nodeNo) { if(tree[nodeNo].visited)return tree[nodeNo].dominatedCountryNum; tree[nodeNo].visited = 1; tree[nodeNo].dominatedCountryNum=1; for(int i = 0; i< tree[nodeNo].count;++i) { tree[nodeNo].dominatedCountryNum+=calcuDonimatedNum(tree[nodeNo].children[i]); } return tree[nodeNo].dominatedCountryNum; } void work(int nodeNo) { if(tree[nodeNo].visited)return; tree[nodeNo].visited = 1; dp[nodeNo][0] = 0; for(int i = 0; i < tree[nodeNo].count;++i)work(tree[nodeNo].children[i]); for(int i = 0; i< tree[nodeNo].count;++i) { int son = tree[nodeNo].children[i]; for(int j = tree[nodeNo].dominatedCountryNum-1;j>0;--j) { for(int k = 0;k<=tree[son].dominatedCountryNum;++k) if(dp[nodeNo][j] > (dp[nodeNo][j-k] + dp[son][k])){ dp[nodeNo][j] = (dp[nodeNo][j-k] + dp[son][k]); } } } dp[nodeNo][tree[nodeNo].dominatedCountryNum] = tree[nodeNo].cost; } int main() { FILE * file = fopen("./file","r"); if(file == NULL) { file = stdin; } char countryname[MAX_COUNTRY_NAME]; while(true) { char c = 0; int n = 0, m = 0,v = 0,donimatedNo = 0,countryNo = 0; fscanf(file,"%d%d",&n,&m); if(n < m || n <=0)break; init(n); for(int i = 0; i<n;i++) { fscanf(file,"%s%d",countryname,&v); countryNo = findCountry(countryname); tree[countryNo].cost = v; while(getc(file)==' ') { fscanf(file,"%s",countryname); donimatedNo=findCountry(countryname); tree[donimatedNo].isChild = 1; tree[countryNo].children[tree[countryNo].count++] = donimatedNo; } } for(int i = 1; i<=n;++i) { if(tree[i].isChild == 0) { tree[0].children[tree[0].count++]=i; } } calcuDonimatedNum(0); for(int i = 0; i<=n;i++) { tree[i].visited = 0; } work(0); int asn = MAX; for(int i = m;i<=n;i++){ if(dp[0][i] < asn)asn = dp[0][i]; } printf("%d/n",asn); } }