树形dp小结——2

树形dp的题一般都结合着背包来用。以下的几道题都是结合着背包的思想来的

1:树形dp+分组背包     状态比较难想

之前说过在最长距离的那道题里说过,不会返回。但是有的提示需要考虑返回的节点的。

下面就是一个例子:

 

Apple Tree

 

题目大意:

给你一个苹果树,有N个节点,每个节点上都有一个一个苹果也就有一个权值,当你经过这个点将得到权值,重复走节点只能算一次,给N-1条边。问你走k步能得到的最大权值和。

dp[i][j][0]表示从i节点出发最后回到i节点花费最多j步能获得的最大值, 
dp[i][j][1]表示从j节点出发最后不回到i节点花费最多j步能获得的最大值 
dp[root][j][0] = max(dp[root][j-k][0] + dp[son][k-2][0])
//root->son 和 son->root 共花费2步,j-k是在其他儿子花费的步数
dp[root][j][1] = max(dp[root][j-k][0] + dp[son][k-1][1])
dp[root][j][1] = max(dp[root][j-k][1] + dp[son][k-2][0])
//只需要回到root一次,一种情况是先走其他儿子回到root再走son,
//另一种情况是先走son这个儿子并回到root再去走其他儿子 

代码:

#include <iostream>  
#include <stdio.h>  
#include <string.h>  
using namespace std;  
  
const int M=210;  
int wi[M];  
int dp[M][M][2];//0不回来,1回来  
int n,m;  
int ne;  
struct Edge  
{  
    int to,nxt;  
}edge[M*2];  
int st[M];  
void add_edge(int fr,int to)  
{  
    edge[ne].to=to;  
    edge[ne].nxt=st[fr];  
    st[fr]=ne++;  
}  
int isu[M];  
void dfs(int rt)
{  
    isu[rt]=1;  
    for(int i=0;i<=m;i++)  
        dp[rt][i][0]=dp[rt][i][1]=wi[rt];  
    for(int i=st[rt];i!=-1;i=edge[i].nxt)  
    {  
        int t=edge[i].to;  
        if(isu[t])  
            continue;  
        dfs(t);  
        for(int j=m;j>=1;j--)  
        {  
            for(int k=1;k<=j;k++)  
            {  
                dp[rt][j][0]=max(dp[rt][j][0],dp[rt][j-k][1]+dp[t][k-1][0]);  
                dp[rt][j][0]=max(dp[rt][j][0],dp[rt][j-k][0]+dp[t][k-2][1]);  
                dp[rt][j][1]=max(dp[rt][j][1],dp[rt][j-k][1]+dp[t][k-2][1]);  
            }  
        }  
    }  
}  
  
int main()  
{  
    int a,b;  
    while(scanf("%d%d",&n,&m)!=EOF)  
    {  
        memset(dp,0,sizeof(dp));  
        ne=0;  
        memset(st,-1,sizeof(st));  
        memset(isu,0,sizeof(isu));  
        //m++;  
        for(int i=1;i<=n;i++)  
            scanf("%d",&wi[i]);  
        for(int i=1;i<=n-1;i++)  
        {  
            scanf("%d%d",&a,&b);  
            add_edge(a,b);  
            add_edge(b,a);  
        }  
        dfs(1);  
        printf("%d\n",max(dp[1][m][0],dp[1][m][1]));  
    }  
    return 0;  
}  

2:

The more, The Better

 HDU - 1561 

题目大意:

ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物。但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮ACboy算出要获得尽量多的宝物应该攻克哪M个城堡吗? 

思路分析:给你一棵树,欲取子节点,必取父节点。问取的价值最多为多少。

简单的树形+背包

代码:

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<vector>
#define MAX 500
using namespace std;
vector<int>q[MAX];
int dp[MAX][MAX];
int v[MAX];
void dfs(int n,int m)
{
    int len=q[n].size();
    int k,h;
    dp[n][1]=v[n];//打掉该点获得的财富值
    for(int i=0;i<len;i++)//去攻击他的下边的节点
    {
        if(m>1)//次数还没有超,还能再攻击
            dfs(q[n][i],m-1);
        for(int j=m;j>=1;j--)
        {
            h=j+1;
            for(int k=1;k<h;k++)//攻击子节点的次数
            {
                if(dp[n][h]<dp[n][h-k]+dp[q[n][i]][k])//k下打非这个子树里的
                    dp[n][h]=dp[n][h-k]+dp[q[n][i]][k];
            }
        }
    }
}

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF,n||m)
    {
        int i,p;
        memset(dp,0,sizeof(dp));
        memset(v,0,sizeof(v));
        for(int i=0;i<=n;i++)
            q[i].clear();
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&p,&v[i]);
            q[p].push_back(i);
        }
        dfs(0,m+1);
        cout<<dp[0][m+1]<<endl;
    }
}

3.背包问题

Rebuilding Roads

 POJ - 1947 

题目大意:有n个点组成一个树,问至少要删除多少条边才能获得一棵有p个结点的子树 

题目分析:

dp[i][j]表示以i号节点为根的子树,当有j个结点时最少需要去掉几条边。

初始化:当只有1个节点时,一定是连接它到孩子结点的所有边都去掉。

设某一孩子结点标号为v  则dp[i][j]=min(dp[i][j],dp[i][j-t]+dp[v][t]-1);

记录最小值是时,如果节点不是根节点,那么总值 需要加1,因为还有连接父亲结点的一条边没算。

代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<cmath>
using namespace std;
struct node
{
    int to;
    int next;
};
int dp[155][155];
int brother[155];
int son[155];
int n,p;
void dfs(int r)
{
    for(int i=0;i<=p;i++)
    {
        dp[r][i]=1000000000;
    }
    dp[r][1]=0;
    for(int i=son[r];i!=-1;i=brother[i])
    {
        int v=i;
        dfs(v);
        for(int j=p;j>=0;j--)
        {
            int tmp=dp[r][j]+1;
            for(int k=0;k<=j;k++)
            {
                tmp=min(tmp,dp[v][j-k]+dp[r][k]);
            }
            dp[r][j]=tmp;
        }
    }
}
int main()
{
    int u,v;
    while(~scanf("%d%d",&n,&p))
    {
        int cnt=0;
        memset(son,-1,sizeof(son));
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            brother[v]=son[u];//u为父节点,v的兄弟就是u的孩子
            son[u]=v;

        }
        int ans;
        dfs(1);
        ans=dp[1][p];
        for(int j=1;j<=n;j++)
        {
            if(ans>dp[j][p])
                ans=dp[j][p]+1;//不要忘记减去父亲的那条(除了根结点)
        }
        cout<<ans<<endl;
    }
    return 0;

}

4.背包  +树的重心变形

题目:Tree Cutting  poj2378

给一个树状图,有n个点。求出,去掉哪个点,使得剩下的每个连通子图中点的数量不超过n/2。如果有很多这样的点,就按升序输出。n<=10000 

思路分析:

此题是求解树的重心的一个变形。

根据求解树的重心的方法,设f[i] 为 以i为根的子树的结点个数,那么 f[i] += {f[j] + 1 | j 为i的子结点} 。

设dp[i] 为删除结点i, 最大的连通图有多少个结点。最后答案将d[i] <= n / 2 的结点 i 输出即可。

 

代码:

 

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int MAX=50010;
struct lalala   //建结构体
{
    int next;
    int to;
}edge[MAX*2];

int head[MAX];//前驱节点
int vis[MAX];//是否访问过,做标记
int dp[MAX];//记录拥有最大的节点数
int num[MAX];//统计以每个结点为根的树的结点数,记为num[].
int n, tot;

void add(int u, int v)//建树
{
    edge[++tot].next=head[u];
    edge[tot].to=v;
    head[u]=tot;
}


void init()
{
    tot=0;
    memset(dp,0,sizeof(dp));
    memset(vis,0,sizeof(vis));
    memset(num,0,sizeof(num));
    memset(edge,0,sizeof(edge));
}


void dfs(int u)
{
    vis[u]=1;
    num[u]=1;
    for(int i=head[u];i!=0;i=edge[i].next)
    {
        int v=edge[i].to;
        if(vis[v])
            continue;
        dfs(v);
        dp[u]=max(dp[u],num[v]);
        num[u]+=num[v];

    }
    dp[u]=max(dp[u],n-num[u]);
}
int main()
{
    int a, b;
    while(~scanf("%d", &n))
    {
        init();
        for(int i=1; i<=n-1; i++)
        {
            scanf("%d%d", &a, &b);
            add(a, b);
            add(b, a);
        }
        dfs(1);
        int ans[MAX];
        int k=0;
        for(int i=1; i<=n; i++)
        {
            if(dp[i]<=n/2)
                ans[++k]=i;
        }
        for(int i=1; i<=k; i++)
        {
            printf("%d\n", ans[i]);

        }
    }

    return 0;
}

a未完待续。。。。。。

 

 

 

 


 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值