树形DP(HDOJ1011 2196 4003 5148 POJ2342)

自己整理了个比较水的模版...一般简单的树形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;
}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值