自己整理了个比较水的模版...一般简单的树形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;
}
HDOJ 2196
题目大意:略
输入 边终点,权,边起点默认为编号+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;
}
POJ2342
题目大意:给一棵树,点权,父亲孩子不能同时出现,求树的最大权
输入 点数,点权,边
输出 独立集最大权
状态转移方程:
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;
}
HDOJ5148
输入 点数,选择的点数
始边,终边,边权
输出 选择的点两两直接的总距离和乘2
#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;
}