自己整理了个比较水的模版...一般简单的树形DP题基本可以解....
用HDOJ 4003举例
题目大意:给一棵树,以及每人通过此边的花费,求用给定人数遍历树的最小花费
输入 点数,根位置,人数
始边,终边,每人通过此边的花费
输出 用给定人数遍历树的最小花费
注意:人可以往回走,用dp[p][0]记录
状态转移方程:
dp[p][j]=min(dp[p][j],dp[p][j-k]+dp[son][k]+k*dv[p][i].cos)
#include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <iostream> using namespace std; const int MAXN = 10010; const int MAXM = 11; int n,m,root; int dp[MAXN][MAXM]; struct Edge{ int next,cos; Edge(); Edge(int b,int c){next=b;cos=c;} }; vector<Edge>dv[MAXN]; void init() { for(int i=0;i<=n;i++) dv[i].clear(); memset(dp,0,sizeof(dp)); } void dfs(int p,int fa) { int son; for(int i=0;i<dv[p].size();i++) { son=dv[p][i].next; if(son^fa){//不往回走 dfs(son,p); for(int j=m;j>=0;j--)//使用j人搜索父亲(需要用到更小j的记录,循环不可反) { dp[p][j]+=dp[son][0]+2*dv[p][i].cos;//初始为可能的最大值消费 for(int k=1;k<=j;k++)//使用k人搜索t孩子,j-k人搜索其他孩子(循环可反) { dp[p][j]=min(dp[p][j],dp[p][j-k]+dp[son][k]+k*dv[p][i].cos); } } } } } int main() { #ifdef DEBUG freopen("CBin.txt","r",stdin); //freopen("CBout.txt","w",stdout); #endif while(~scanf("%d%d%d",&n,&root,&m)) { if(n==-1&&m==-1)break; init(); for(int i=1;i<n;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); dv[a].push_back(Edge(b,c)); dv[b].push_back(Edge(a,c)); } //int root=1; dfs(root,0); printf("%d\n",dp[root][m]); } return 0; }
HDOJ 1011
题目大意:给一棵树,点消耗,点价值,求用这些人获得的最大价值,根位置默认1
输入 点数,人数
过该点消耗的人数(每20一人),点价值
始边,终边
输出 分配这些人,输出获得的最大价值
注意:有人经过的区域才能得到该点的价值,即使该点花费为0人
状态转移方程:
dp[p][j]=max(dp[p][j],dp[p][j-k]+dp[son][k])
#include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <iostream> using namespace std; const int MAXN = 110; int n,m; int dp[MAXN][MAXN],cos[MAXN],w[MAXN]; struct Edge{ int next; Edge(); Edge(int b){next=b;} }; vector<Edge>dv[MAXN]; void init() { for(int i=0;i<=n;i++) dv[i].clear(); memset(dp,0,sizeof(dp)); memset(cos,0,sizeof(cos)); memset(w,0,sizeof(w)); } void dfs(int p,int fa) { int son; int t=(cos[p]+19)/20;//攻打消费 for(int i=t;i<=m;i++) dp[p][i]=w[p]; for(int i=0;i<dv[p].size();i++) { son=dv[p][i].next; if(son^fa){ dfs(son,p); for(int j=m;j>=t;j--)//使用j打父亲(需要用到更小j的记录,循环不可反) { for(int k=1;k<=j-t;k++)//使用k打t孩子,j-k打其他孩子(循环可反) dp[p][j]=max(dp[p][j],dp[p][j-k]+dp[son][k]); } } } } int main() { #ifdef DEBUG freopen("CBin.txt","r",stdin); //freopen("CBout.txt","w",stdout); #endif while(~scanf("%d%d",&n,&m)) { if(n==-1&&m==-1)break; init(); for(int i=1;i<=n;i++) { scanf("%d%d",cos+i,w+i); } for(int i=1;i<n;i++) { int b,c; scanf("%d%d",&b,&c); dv[b].push_back(Edge(c)); dv[c].push_back(Edge(b)); } if(m==0){printf("0\n");continue;} int root=1; dfs(root,0); printf("%d\n",dp[1][m]); } return 0; }
题目大意:略
输入 边终点,权,边起点默认为编号+1
输出 所有的,点到各点的路径最大权
这代码也可以求树的直径
#include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <iostream> using namespace std; const int MAXN = 10010; int n,e,s,m; int dx[MAXN],dy[MAXN]; struct Edge{ int next,w; Edge(); Edge(int a,int b){next=a;w=b;} }; vector<Edge>dv[MAXN]; void init() { for(int i=0;i<=n;i++) dv[i].clear(); memset(dx,0,sizeof(dx)); memset(dy,0,sizeof(dy)); } void dfs(int p,int fa,int *d) { int son; for(int i=0;i<dv[p].size();i++) { son=dv[p][i].next; if(son^fa){ d[son]=d[p]+dv[p][i].w; dfs(son,p,d); } } } int main() { #ifdef DEBUG freopen("CBin.txt","r",stdin); //freopen("CBout.txt","w",stdout); #endif while(~scanf("%d",&n)) { init(); for(int a=2;a<=n;a++) { int b,c; scanf("%d%d",&b,&c); dv[a].push_back(Edge(b,c)); dv[b].push_back(Edge(a,c)); } dfs(1,0,dx);//任意一个起点dx[1]=0 int x,y; x=1; for(int i=2;i<=n;i++) { if(dx[i]>dx[x]) x=i; } memset(dx,0,sizeof(dx)); dfs(x,0,dx);//距离1节点最远的节点x作为起点 y=1; for(int i=2;i<=n;i++) { if(dx[i]>dx[y]) y=i; } dfs(y,0,dy);//距离x节点最远的节点y作为起点,xy为树的直径 for(int i=1;i<=n;i++) printf("%d\n",max(dx[i],dy[i])); } return 0; }
题目大意:给一棵树,点权,父亲孩子不能同时出现,求树的最大权
输入 点数,点权,边
输出 独立集最大权
状态转移方程:
dp[node][1] += dp[i][0];//选父亲,不选孩子
dp[node][0] +=max(dp[i][1],dp[i][0]);//不选父亲,不选孩子
#include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <iostream> using namespace std; const int MAXN = 10010; int n,e,s,m; int dp[MAXN][2]; struct Edge{ int next; Edge(); Edge(int b){next=b;} }; vector<Edge>dv[MAXN]; void init() { for(int i=0;i<=n;i++) dv[i].clear(); memset(dp,0,sizeof(dp)); } void dfs(int p,int fa) { int son; for(int i=0;i<dv[p].size();i++) { son=dv[p][i].next; if(son^fa){ dfs(son,p); dp[p][1] += dp[son][0]; dp[p][0] +=max(dp[son][1],dp[son][0]); } } } int main() { #ifdef DEBUG freopen("CBin.txt","r",stdin); //freopen("CBout.txt","w",stdout); #endif while(~scanf("%d",&n)) { if(!n)continue; init(); for(int i=1;i<=n;i++) { scanf("%d",&dp[i][1]); } for(int i=1;i<n;i++) { int b,c; scanf("%d%d",&b,&c); dv[b].push_back(Edge(c)); dv[c].push_back(Edge(b)); } int root=1;//任意选一个起点 dfs(root,0); printf("%d\n",max(dp[root][0],dp[root][1])); } return 0; }
#include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <iostream> using namespace std; const int MAXN = 2005; #define INF 999999999999LL typedef long long LL; int n,m,k; LL dp[MAXN][55];//dp[p][k]代表节点p的k-1个后代之间两两的距离总和 struct Edge{ int next,cos; Edge(); Edge(int b,int c){next=b;cos=c;} }; vector<Edge>dv[MAXN]; void init() { for(int i = 1;i <= n;i++) { dv[i].clear(); dp[i][0] = 0;//1个节点没距离可言 for(int j = 1;j <= k;j++) { dp[i][j] = INF; } } } void dfs(int p,int fa) { int son; for(int b=0;b<dv[p].size();b++) { son=dv[p][b].next; if(son^fa){ dfs(son,p); for(int i=k-1;i>=1;i--)//下发i个 { for(int j=1;j<=i;j++){//该子节点分配到j个 if(dp[p][i-j]==INF||dp[son][j-1] == INF)continue; dp[p][i]=min(dp[p][i],dp[p][i-j]+dp[son][j-1]+dv[p][b].cos*(k-j)*(j));//dp[son][j-1]注意j要减1 } } } } } int main() { int T; cin>>T; while(T--) { scanf("%d%d",&n,&k); init(); for(int i=1;i<n;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); dv[a].push_back(Edge(b,c)); dv[b].push_back(Edge(a,c)); } LL ans=INF; dfs(1,0); for(int i = 1;i <= n;i++)//不一定包含根节点 { ans = min(ans,dp[i][k-1]); } cout<<ans*2<<'\n'; } return 0; }