【树形DP&背包问题】 ◆TopCoder D2L3◆ CollectingTokens

◆TopCoder D2L3◆

CollectingTokens


□目录□


□题目□

Problem Statement

Surya has a tree with n nodes, numbered 1 through n. Each node contains some arbitrary nonnegative number oftokens.

Surya sometimes goes for a walk on the tree. He has to start his walk in node 1, but he may terminate it in any node of the tree. Surya gets tired easily: during the walk he is only able to traverse at most L edges.

Surya now wants to collect as many tokens as possible during a single walk. He can collect tokens in all nodes he visits, including the nodes where he starts and ends his walk. Obviously, the tokens in each node can only be collected once.

You are given the structure of the tree in int[]s A and B, each with n-1 elements. For each valid i the tree contains an edge between the nodes A[i] and B[i]. You are also given the int[] tokens with n elements. For each valid i, tokens[i] is the number of tokens in node i+1. Finally, you are given the int L.

Return the maximum number of tokens Surya can collect.

Definition

Class: CollectingTokens

Method: maxTokens

Parameters: int[], int[], int[], int

Returns: int

Method signature:int maxTokens(int[] A, int[] B, int[] tokens, int L)

(be sure your method is public)

Constraints
  • n will be between 1 and 50, inclusive.
  • A and B will contain exactly n-1 elements each.
  • Each element of A and B will be between 1 and n, inclusive.
  • A and B will define a tree.
  • tokens will contain exactly n elements.
  • Each element of tokens will be between 1 and 100, inclusive.
    MENU
  • L will be between 1 and 100, inclusive.
Examples

(1)

{1}
{2}
{7,1}
6
Returns: 8

This tree consists of two nodes and a single edge. There are 7 tokens in node 1 and 1 token in node 2. Surya can make at most six steps, which is more than enough to collect all 7+1 = 8 tokens.

(2)

{3,1}
{2,2}
{2,3,9}
5
Returns: 14

(3)

{1,2,5,3}
{4,4,1,4}
{6,1,6,4,4}
3
Returns: 16

This is a tree with five nodes. One optimal walk for this tree is to start in node 1, go to node 4, then to node 3, and then back to node 4. As L=3, this is the longest walk Surya may make. During this walk he will collect 6 tokens in node 1, 4 tokens in node 4, 6 tokens in node 3, and then 0 tokens when revisiting node 4.

The total is 6+4+6+0 = 16 tokens. Another optimal walk is to start in node 1, go to node 4, then to node 3, and t o stop there. Surya is not required to make all L steps.

(4)

{9,1,7,10,5,8,3,4,2}
{6,6,9,6,6,1,1,6,6}
{4,2,1,6,3,7,8,5,2,9}
4
Returns: 26

(5)

{25,22,35,42,40,9,32,12,37,44,23,1,24,28,20,4,26,33,11,48,34,6,16,50,46,17,8,43,18,30,31,36,39,13,10,45,3,47,15,2,29,19,7,14,41,49,38,27,21}
{5,5,25,25,25,42,25,40,5,35,25,32,42,9,32,23,40,25,20,33,26,37,12,1,48,24,22,25,11,24,48,34,18,9,50,42,16,40,1,10,47,22,48,44,48,1,4,46,47}
{6,9,4,9,5,8,6,4,4,1,4,8,3,4,5,8,5,6,4,9,7,9,7,9,5,2,7,2,7,7,5,9,5,8,5,7,1,9,3,9,3,6,4,5,5,4,7,9,2,2}
48
Returns: 194

□解析□

◇从头讲起——输入

由于老师在考这道题的时候没有按照原题的输入格式,所以大家不要看最下面的代码 : )
因为原题并没有给出点的个数,而是以换行结束,这里给大家介绍一个方法——用读入优化来解决这道题的输入:

bool Read(int &x) //x是读入的变量,返回该行是否结束(1结束,0继续)
{
    int a=0,b=1;
    char c=getchar();
    while('0'>c || c>'9')
    {
        b=c=='-'? -1:b;c=getchar();
        if(c=='\n')
            return 1;
    }
    while('0'<=c && c<='9')
        a=a*10+c-'0',c=getchar();
    return c=='\n';
}

那么我们就只需要用一个while就可以解决了。

int i=0,A[MAXN+5]={};
while(!Read(A[i])) A++; //该行没有结束

啊哈,输入就这么解决了(:-x)

◇树形结构

虽然这是一个树形结构,但是我还是存了双向边,以防意外。

围绕树形结构,我们能够很自然地想到树形DP——又因为树不存在环,所以若要搜索,也是不需要记忆化的(这就是我用搜索的原因)。先求出其子树的状态,再根据每一个子树推导根。

然后利用STL vector<>来储存邻接表(of course 可以用链表 :-P)

◇DP上树

按照题目意思,1是整棵树的根,所以从点1开始向深处搜索。

枚举当前节点的每一个儿子,即连通点,但是注意避免访问到父亲节点(毕竟存的是双向边);用一个vis数组判断重复访问即可。接下来就是整个问题的核心——

因为一个节点可能有很多个儿子,所以我们要合理分配每一个子树走多深,即分配长度,既要使步数不超界,还要使得到的分值最大。然后就很容易想到背包问题——对每一个节点计算背包最优值。是不是莫名感到熟悉?像不像铲雪车问题?我们很容易想到,如果有一个节点具有多个儿子,我们可以不必走完其中的一个子树,可以访问多个子节点来完成最优解。

那么就涉及到一个问题——出现了2种情况:①从根节点u进入子树A后不返回u节点;②从根节点进入子树A后返回u节点。这两者唯一的区别就是使用的步数不同,那么针对这一点,我们将状态定义如下:

a) dp[u][n][1]:当前在u节点,剩余n步,不返回u节点;

b) dp[u][n][0]:当前在u节点,剩余n步,返回u节点;

由于是树状结构,不存在环,所以存在以下最优定论(很简单,但是很难描述,我已经尽力了 T^T):

1.若不返回u节点,则u的所有子树中最多只能有一个不返回其根节点;
2.若返回u节点,则u的所有子树中都需要返回其根节点;

en?没有什么问题,接下来就是一个简单的背包问题——分配步数给每一棵子树。

状态转移方程式:设当前准备访问 u 的子节点 v ,剩余 j 个步数,当回到u节点时剩余 k 步;
1. 如果返回节点u,即dp[u][j][0],即分配给 v 子树 j-k-2 个步数(因为要返回u节点,则经过边 u->v 2次,所以减2),则:dp[u][j][0] = max{ dp[v][j-k-2][0] + dp[u][k][0] }
2. 如果不返回节点u,即dp[u][j][1],但是要从v返回到u(不在子树v中结束,而在其他子树中结束),则分配给 v 子树 j-k-2 个步数,则 dp[u][j][1] = max{ dp[v][j-k-2][0] + dp[u][k][1] }
3. 如果不返回节点u,即dp[u][j][1],且在子树 v 中结束,则分配给 v 子树 j-k-1 个步数,则 dp[u][j][1] = max{ dp[u][k][0]+dp[v][j-k-1][1] }

就这么多了。o(≧ v ≦)o~~


□代码片□

改了一下输入~

输入第一行:n-1的值;

第二行和第三行分别为 A[] 、B[];

第四行为 tokens[];

第五行为 L;

没有按照TopCoder的要求打包成一个类(class)QwQ

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
#define MAXN 50
int A[MAXN+5],B[MAXN+5],val[MAXN+5],dp[MAXN+5][2*MAXN+5][2],l;
bool vis[MAXN+5];
vector<int> lec[MAXN+5];
void Search(int u,int fa) //实际上不需要fa
{
    for(int i=0;i<=l+1;i++)
        dp[u][i][0]=dp[u][i][1]=val[u];
    for(int i=0;i<lec[u].size();i++)
        if(!vis[lec[u][i]] && lec[u][i]!=fa)
        {
            int v=lec[u][i];
            vis[v]=true;
            Search(v,u);
            for(int j=l;j>=0;j--)
                for(int k=0;k<j;k++)
                {
                    if(j-k-2>=0)
                        dp[u][j][0]=max(dp[u][j][0],dp[u][k][0]+dp[v][j-k-2][0]),
                        dp[u][j][1]=max(dp[u][j][1],dp[u][k][1]+dp[v][j-k-2][0]);
                    if(j-k-1>=0)
                        dp[u][j][1]=max(dp[u][j][1],dp[u][k][0]+dp[v][j-k-1][1]);
                }
        }
}
int main()
{
    freopen("collectingtokens.in","r",stdin);
    freopen("collectingtokens.out","w",stdout);
    int n=0;scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&A[i]);
    for(int i=1;i<=n;i++)
        scanf("%d",&B[i]);
    for(int i=1;i<=n;i++)
        lec[A[i]].push_back(B[i]),lec[B[i]].push_back(A[i]);
    for(int i=1;i<=n+1;i++)
        scanf("%d",&val[i]);
    scanf("%d",&l);
    vis[1]=true;
    Search(1,-1);
    printf("%d\n",max(dp[1][l][0],dp[1][l][1]));
    return 0;
}

The End

Thanks for reading!

-Lucky_Glass

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值