树上背包问题小结

by Wine93 2014.1.15

 

 

2.训练题目

1.POJ 1155 TELE(树形DP+分组背包) http://poj.org/problem?id=1155

题意:给定一棵边权全为负,叶子结点为正权的有根树! 问从根结点出发使得权值和不为负最多能到达的叶子结点个数。

思路:树形DP+分组背包!

  dp[u][j]:以u为跟结点,选j个叶子结点最大价值

  dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-e[i].w);     (v为u的子结点)

 

# include<map>
# include<set>
# include<cmath>
# include<queue>
# include<stack>
# include<vector>
# include<string>
# include<cstdio>
# include<cstring>
# include<iostream>
# include<algorithm>
# include<functional>
using namespace std;

typedef pair<int,int> PII;
# define MOD 1000000007
# define LL long long
# define pb push_back
# define F first
# define S second
# define INF 1<<30
# define N 3005
int num,head[N];
int n,m,money[N];
int dp[N][N],leaf[N];

struct edge
{
    int u,v,w,next;
}e[N];

void addedge(int u,int v,int w)
{
    e[num].u=u;
    e[num].v=v;
    e[num].w=w;
    e[num].next=head[u];
    head[u]=num++;
}

void Count(int u)
{
    int i,v;
    if(u>n-m) leaf[u]=1;
    else leaf[u]=0;
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        Count(v);
        leaf[u]+=leaf[v];
    }
}

void dfs(int u)
{
    int i,j,k,v;
    if(u>n-m)   //是否为叶子结点
    {
        dp[u][1]=money[u];
        return;
    }
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        dfs(v);
        for(j=leaf[u];j>=1;j--)
            for(k=1;k<=leaf[v]&&k<=j;k++)
                dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-e[i].w);
    }
}

void init(int n)
{
    int i,j;
    num=0;
    memset(head,-1,sizeof(head));
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            dp[i][j]=-INF;
}

int main()
{
 //   freopen("in.txt","r",stdin);
    int i,j,k,u,v,c,ans;
    while(scanf("%d%d",&n,&m)!=EOF)  //n个结点 m个叶子结点
    {
        ans=0;
        init(n);
        for(u=1;u<=n-m;u++)
        {
            scanf("%d",&k);  //k个儿子
            for(i=1;i<=k;i++)
            {
                scanf("%d%d",&v,&c);
                addedge(u,v,c);
            }
        }
        for(i=1;i<=m;i++)
            scanf("%d",&money[n-m+i]);
        Count(1);
        dfs(1);
        for(i=n-1;i>=1;i--)
            if(dp[1][i]>=0)
            {
                ans=i;
                break;
            }
        printf("%d\n",ans);
    }
    return 0;
}
POJ 1155

 

 2.POJ 1947 Rebuilding Roads(树形DP+分组背包+2遍dfshttp://poj.org/problem?id=1947

题意:给定一棵树,求遗留下P个结点的子树,最少需要断几条边

思路:树形DP+分组背包

1.第一步dfs计算出cut[u][i]:以u为结点切除i个结点最少需要断几条边

2.第二次dfs计算出left[u][i]:以u为结点留下i个结点最少需要断几条边

3.遍历所有的顶点,取left[u][p]的最小值就是答案

# include<map>
# include<set>
# include<cmath>
# include<queue>
# include<stack>
# include<vector>
# include<string>
# include<cstdio>
# include<cstring>
# include<algorithm>
# include<functional>
using namespace std;

typedef pair<int,int> PII;
# define MOD 1000000007
# define LL long long
# define pb push_back
# define F first
# define S second
# define N 205
# define M 305
int num,head[N];
int son[N],cut[N][N],left[N][N];

struct edge
{
    int u,v,next;
}e[N];

void addedge(int u,int v)
{
    e[num].u=u;
    e[num].v=v;
    e[num].next=head[u];
    head[u]=num++;
}

void getson(int u,int fa)
{
    int i,v;
    son[u]=1;
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        getson(v,u);
        son[u]+=son[v];
    }
}

void dfs(int u,int fa)
{
    int i,j,k,n,v;
    cut[u][son[u]]=1;
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        dfs(v,u);
        for(j=son[u];j>=1;j--)
            for(k=1;k<=son[v]&&k<=j;k++)
                cut[u][j]=min(cut[u][j],cut[u][j-k]+cut[v][k]);
    }
}

//left[u][i]:已u为结点保存i个
void getleft(int u,int fa)
{
    int i,v;
    if(fa==0) left[u][son[u]]=0;
    else left[u][son[u]]=1;
    for(i=1;i<son[u];i++)
        left[u][i]=cut[u][son[u]-i]+(fa!=0);
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        getleft(v,u);
    }
}

void init(int n)
{
    int i,j;
    num=0;
    memset(head,-1,sizeof(head));
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            cut[i][j]=left[i][j]=1<<30;
}

int main()
{
    //freopen("in.txt","r",stdin);
    int i,n,p,u,v,ans;
    while(scanf("%d%d",&n,&p)!=EOF)
    {
        ans=1<<30;
        init(n);
        for(i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        if(p==n)
        {
            printf("0\n");
            continue;
        }
        getson(1,0);
        dfs(1,0);
        getleft(1,0);
        for(i=1;i<=n;i++)
            ans=min(ans,left[i][p]);
        printf("%d\n",ans);
    }
    return 0;
}
POJ 1947

 

 3.HDU 1561 The more, The Better (树形DP+分组背包)http://acm.hdu.edu.cn/showproblem.php?pid=1561

题意:在一棵树上选择若干个点获得的最大价值,选子节点必须先选父节点

思路:

# include<map>
# include<set>
# include<cmath>
# include<queue>
# include<stack>
# include<vector>
# include<string>
# include<cstdio>
# include<cstring>
# include<iostream>
# include<algorithm>
# include<functional>
using namespace std;

typedef pair<int,int> PII;
# define MOD 1000000007
# define LL long long
# define pb push_back
# define F first
# define S second
# define N 205
int num,head[N];
int w[N];
int son[N],dp[N][N];

struct edge
{
    int u,v,next;
}e[N];

void addedge(int u,int v)
{
    e[num].u=u;
    e[num].v=v;
    e[num].next=head[u];
    head[u]=num++;
}

void getson(int u,int fa)
{
    int i,v;
    son[u]=1;
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        getson(v,u);
        son[u]+=son[v];
    }
}

void dfs(int u,int fa)
{
    int i,j,k,v,limit=(u!=0);
    dp[u][1]=w[u];
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        dfs(v,u);
        for(j=son[u];j>=1;j--)
            for(k=1;k<=son[v]&&k<=j-limit;k++)
                dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    }
}

void init()
{
    num=0;
    memset(head,-1,sizeof(head));
}

int main()
{
    //freopen("in.txt","r",stdin);
    int i,n,m,u,v;
    while(scanf("%d%d",&n,&m)!=EOF&&(n+m))
    {
        init();
        memset(dp,0,sizeof(dp));
        for(v=1;v<=n;v++)
        {
            scanf("%d%d",&u,&w[v]);
            addedge(u,v);
        }
        getson(0,-1);
        dfs(0,-1);
        printf("%d\n",dp[0][m]);
    }
    return 0;
}
HDU 1561

 

 

 4.ZOJ 3626 Treasure Hunt I(树形DP+分组背包) http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4772

题意:给定一棵结点权值(获得价值),边权(消耗步数)树! 求从k点出发最多走m步并且回到k点获得的最大价值和!

思路:简单树形背包 dp[u][i]:以u为结点走i步并且回到u的最大价值和!

# include<map>
# include<set>
# include<cmath>
# include<queue>
# include<stack>
# include<vector>
# include<string>
# include<cstdio>
# include<cstring>
# include<iostream>
# include<algorithm>
# include<functional>
using namespace std;

typedef pair<int,int> PII;
# define MOD 1000000007
# define LL long long
# define pb push_back
# define F first
# define S second
# define N 105
# define M 205
int num,head[N];
int n,s,m,w[N];   //n:结点  s:出发点 m:步数
int dp[N][M];  //dp[i][j]:i结点走j步回到i的最大价值

struct edge
{
    int u,v,w,next;
}e[M];

void addedge(int u,int v,int w)
{
    e[num].u=u;
    e[num].v=v;
    e[num].w=w;
    e[num].next=head[u];
    head[u]=num++;
}

void dfs(int u,int fa)
{
    int i,j,k,v;
    for(i=0;i<=m;i++) dp[u][i]=w[u];
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        dfs(v,u);
        for(j=m;j>=2*e[i].w;j--)   // 走j步
            for(k=0;k<=j-2*e[i].w;k++)
                dp[u][j]=max(dp[u][j],dp[u][j-k-2*e[i].w]+dp[v][k]);
    }
}

void init()
{
    num=0;
    memset(head,-1,sizeof(head));
    memset(dp,0,sizeof(dp));
}

int main()
{
    int i,u,v,c;
    while(scanf("%d",&n)!=EOF)
    {
        init();
        for(i=1;i<=n;i++)
            scanf("%d",&w[i]);
        for(i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&c);
            addedge(u,v,c);
            addedge(v,u,c);
        }
        scanf("%d%d",&s,&m);
        dfs(s,0);
        printf("%d\n",dp[s][m]);
    }
    return 0;
}
ZOJ 3626

 

5.POJ 3345 Bribing FIPA(树形DP+分组背包) http://poj.org/problem?id=3345

题意:给定N个国家,相互之间可能存在附属关系,现在想要至少贿赂m个国家.已知,贿赂一个国家,那么如果该国家拥有附属国,那么他所有附属国都可以算作已经贿赂,求最小贿赂值。

思路:简单树形背包! dp[u][i]:以u为结点贿赂i个国家最少的的话费

注意:题目给的是森林,必须要自己合成树(而不是在森林中找满足个数的最小值,有可能出现个数不足的情况,所以必须得合成树再进行DP)

# include<map>
# include<set>
# include<cmath>
# include<queue>
# include<stack>
# include<vector>
# include<string>
# include<cstdio>
# include<cstring>
# include<iostream>
# include<algorithm>
# include<functional>
using namespace std;

typedef pair<int,int> PII;
# define MOD 1000000007
# define LL long long
# define pb push_back
# define F first
# define S second
# define N 205
int num,head[N];
int n,m,w[N];  //n个国家 至少m个国家支持
int son[N],dp[N][N],vis[N];
int father[N];
int cnt;   //Hash长度
map<string,int> Hash;

struct edge
{
    int u,v,next;
}e[N];

void addedge(int u,int v)
{
    e[num].u=u;
    e[num].v=v;
    e[num].next=head[u];
    head[u]=num++;
}

void getson(int u,int fa)
{
    int i,v; 
    son[u]=1;
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        getson(v,u);
        son[u]+=son[v];
    }
}

void dfs(int u,int fa)
{
    int i,j,k,v;
    for(i=head[u];i!=-1;i=e[i].next)
    {
        v=e[i].v;
        if(v==fa) continue;
        dfs(v,u);
        for(j=son[u];j>=1;j--)   //获得j个国家的支持需要的砖石
            for(k=1;k<=son[v]&&k<=j;k++)
                dp[u][j]=min(dp[u][j],dp[v][k]+dp[u][j-k]);
    }
    if(u)
    for(i=1;i<=son[u];i++)
        dp[u][i]=min(dp[u][i],w[u]);
}

void read(char *s)
{
    int u,v,k=0;
    char *p=strtok(s," ");
    while(p)
    {
        k++;
        if(k==1) 
        {
            if(!Hash[p]) u=Hash[p]=++cnt;
            else u=Hash[p];
        }
        else if(k==2)
            sscanf(p,"%d",&w[u]);
        else
        {
            if(!Hash[p]) v=Hash[p]=++cnt;
            else v=Hash[p];
            father[v]=u;
        }
        p=strtok(NULL," ");
    }
}

void init()
{
    int i,j;
    num=0;
    memset(head,-1,sizeof(head));
    cnt=0;
    Hash.clear();
    for(i=0;i<N;i++)
        for(j=1;j<N;j++)
            dp[i][j]=1<<30;
    memset(father,0,sizeof(father));
}

int main()
{
  //  freopen("in.txt","r",stdin);
    char s[400005];
    int i,ans;
    while(gets(s)&&s[0]!='#')
    {
        sscanf(s,"%d%d",&n,&m);
        init();
        for(i=1;i<=n;i++)
        {
            gets(s);
            read(s);
        }
        for(i=1;i<=n;i++)
            addedge(father[i],i);
        getson(0,-1);
        dfs(0,-1);
        ans=1<<30;
        for(i=0;i<=n;i++)
            ans=min(ans,dp[i][m]);
        printf("%d\n",ans);
    }
    return 0;
}
POJ 3345