树形dp小结——2

来源:互联网 发布:淘宝店铺改名字 编辑:程序博客网 时间:2024/06/06 00:13

树形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 500using 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未完待续。。。。。。












原创粉丝点击