sicily 1019(动态规划)

题目链接:sicily 1019

题目分析:恶心的一笔的一道题,不过确实是一道好题。给你一棵树,n个结点,结点间距离为1,每个结点有一个值,第一次走过某个节点获得该结点的值,问用m步遍历树可以获得总值的最大值。仔细想想这道题其实这道题并不是特别难,只是之前没有做过类似的题目,所以一时半会儿下不了手。

解题思路:
树形DP,什么是树形dp呢?其实不用管那么多的啦,都是动态规划就对了,尤其是这道题,如果一直在想树的话,就永远不能发现它是一个背包了!
1、首先注意到这棵树只有是一棵树这个条件,没有其他的二叉之类的特殊条件,所以不能分成左右两棵子树的方法来做;

2、然后考虑做法,显然,树上的动态规划得用递归来做比较简单,不然还得树结点来个拓扑排序(太麻烦),所以直接递归即可,用vis数组记录结点是否被访问过;

3、然后来考虑状态,很容易想到就是用dp[i][j]表示以 i 为根花费 j 步可以获得的最大值,但是这样的想法很容易看穿有一个漏洞——走完了一个结点的左子树还可以返回然后走右子树啊!那么刚刚的状态就会WA,我们需要对状态进行修正——dp[i][j][0]表示以 i 为根花费 j 步并且不回到 i 结点可以获得的最大值 && dp[i][j][1]表示以 i 为根花费 j 步并且回到 i 结点可以获得的最大值;

4、状态考虑完了,那么来最难的部分——状态转移方程(本人之前做的树形DP比较少)。考虑当前结点index,然后它有很多棵子树,子树的结点保存在vec[index]中,假设现在index有len个子树,那么我们可以把他们想象成len个物品,我们要给每个物品分配若干步数使得总的步数最大,就变成背包问题了(int tmp = vec[index][i]):
我们遍历len个子树,第 i 步结束之后,我们就得到了考虑了vec[index][j](j ≤ i)这 i 个结点之后的所有步数的最大值,看看代码(非递归版的):

for(int i=0;i<len;i++)
{
    int tmp=vec[index][i];
    for(int j=m;j>=1;j--)
    {
        for(int k=1;k<=j;k++)
        {
            if(k>=2)
            {
                dp[index][j][0]=max(dp[index][j][0],
                    dp[index][j-k][0]+dp[tmp][k-2][1]);
                dp[index][j][1]=max(dp[index][j][1],
                    dp[index][j-k][1]+dp[tmp][k-2][1]);
            }
            dp[index][j][0]=max(dp[index][j][0],
                dp[index][j-k][1]+dp[tmp][k-1][0]);
        }
    }
}

有没有觉得很眼熟!这其实就是背包!
第一重循环不说;第二重循环从最大步数开始,逐步算出dp[index][j][0]和dp[index][j][1],怎么算的,就要靠第三重循环了;把 j-k 步给之前算过的所有结点(dp[index][j-k][1]/dp[index][j-k][0]都是只包含之前的子结点的最大值),然后剩下 k 步,分情况考虑:
(1)如果 k ≥ 2,那么可以走了子结点再走回来:
dp[index][j][0] = max(dp[index][j][0] , dp[index][j-k][0] + dp[tmp][k-2][1]);
dp[index][j][1] = max(dp[index][j][1] , dp[index][j-k][1] + dp[tmp][k-2][1]);
(2)必须要算的:
dp[index][j][0] = max(dp[index][j][0] , dp[index][j-k][1] + dp[tmp][k-1][0]);
这就是状态转移方程

5、递归求解!

代码:(递归版的,懒得写拓扑排序)

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MOD 100007

using namespace std;

int n,m,a[105],v[105],dp[105][205][2];
vector<int> vec[105];

void dfs(int index,int sum)
{
    v[index]=1;
    int len=vec[index].size();
    if(!len)
        for(int i=0;i<=m;i++)
            dp[index][i][0]=dp[index][i][1]=a[index];

    dp[index][1][0]=dp[index][1][1]=dp[index][0][0]=dp[index][0][1]=a[index];
    for(int i=0;i<len;i++)
    {
        int tmp=vec[index][i];
        if(v[tmp])
            continue;
        dfs(tmp,sum);
        for(int j=sum;j>=1;j--)
        {
            for(int k=1;k<=j;k++)
            {
                if(k>=2)
                {
                    dp[index][j][0]=max(dp[index][j][0],
                        dp[index][j-k][0]+dp[tmp][k-2][1]);
                    dp[index][j][1]=max(dp[index][j][1],
                        dp[index][j-k][1]+dp[tmp][k-2][1]);
                }
                dp[index][j][0]=max(dp[index][j][0],
                    dp[index][j-k][1]+dp[tmp][k-1][0]);
            }
        }
    }
}

int main()
{
    while(~scanf("%d %d",&n,&m))
    {
        memset(vec,0,sizeof(vec));
        memset(dp,0,sizeof(dp));
        memset(v,0,sizeof(v));
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);

        int x,y;
        for(int i=1;i<n;i++)
        {
            scanf("%d %d",&x,&y);
            vec[x].push_back(y);
            vec[y].push_back(x);
        }

        dfs(1,m);
        printf("%d\n",max(dp[1][m][1],dp[1][m][0]));
    }

    return 0;
}

总结:
1、树上的动态规划!说白了其实也是背包问题,不要想太多~
2、用递归的思路会方便很多,但是有时可能会有爆栈等问题。
3、很神奇的一种题,蛮好玩的。

你可能感兴趣的:(sicily 1019(动态规划))