树形DP (入门) HDU P2196 computer

还50多天noip我猛然发现我还不会树形dp(凉透。。T_T),所以急忙学了一波,因为才刚学这是做的第一道题,如果有错误欢迎在评论区指正,我一定会更改的(嗯)。

树形DP其实就是在树上做的DP,因为树这种结构非常的优美(想出树这种结构的人真 ∗ ∗ ),树本身就带着子结(子树)所以用来做动态规划简直是再好不过的事了。

HDU P2196 computer 是树形DP的一道经典入门题,然而卡了我一小时,最后忍不住看了题解,进行一波copy发现还是不对……检查N遍之后发现是快读的问题,当时心里无数句mmp,我也不知道快读出什么错误。所以以后最好还是不要乱用快读了容易出锅。

思路 :

这道题是求 i 可以到达的最长路径长度是多少。
对于一个有向图 <uv> < u , v > <script type="math/tex" id="MathJax-Element-14">< u ,v ></script>(这题可以当做有向图做)而言
dp[i][0] d p [ i ] [ 0 ] 表示结点 i i 在其子树下的最远距离;
dp[i][1] 表示结点 i i 在其子树下的次远距离;
dp[i][2] 表示结点 i i 通过他的父亲能走的最远距离是多少;

这道题需要两边 dfs

第一遍 dfs d f s :

状态转移方程是 dp[i][0]=dp[j][0]+w[i] d p [ i ] [ 0 ] = d p [ j ] [ 0 ] + w [ i ] 显然,因为 i i j 的父亲结点只有得出 dp[j][0] d p [ j ] [ 0 ] 才能求出 dp[i][0] d p [ i ] [ 0 ] 所以要从下往上枚举

如果 dp[j][0]+w[i]>dp[i][0] d p [ j ] [ 0 ] + w [ i ] > d p [ i ] [ 0 ] 那么就令 dp[i][0]=dp[j][0]+w[i] d p [ i ] [ 0 ] = d p [ j ] [ 0 ] + w [ i ] 并且因为最大值变了那么原来的最大值显然就会变成次大值所以先让 dp[i][1]=dp[i][0] d p [ i ] [ 1 ] = d p [ i ] [ 0 ] 再转移。

如果 dp[j][0]+w[i]<=dp[i][0] d p [ j ] [ 0 ] + w [ i ] <= d p [ i ] [ 0 ] 那么如果 dp[j][0]+w[i]>dp[i][1] d p [ j ] [ 0 ] + w [ i ] > d p [ i ] [ 1 ] 大于次大值就令 dp[i][1]=dp[j][0]+w[i] d p [ i ] [ 1 ] = d p [ j ] [ 0 ] + w [ i ]

第二遍 dfs: d f s :

第二次从上往下,其实就是再遍历一边图,把 dp[i][2] d p [ i ] [ 2 ] 算出来,显然:

dp[v][2]=max(dp[u][2]dp[v][0]+w[i]==dp[u][0]?dp[u][1]:dp[u][0])+w[i] d p [ v ] [ 2 ] = m a x ( d p [ u ] [ 2 ] , d p [ v ] [ 0 ] + w [ i ] == d p [ u ] [ 0 ] ? d p [ u ] [ 1 ] : d p [ u ] [ 0 ] ) + w [ i ] ;

因为 v v u 的儿子要求 v v 就要先知道 u 所以是从上到下

最后的答案就是 maxdp[u][0]dp[u][2] m a x ( d p [ u ] [ 0 ] , d p [ u ] [ 2 ] ) 比较是在子树上的距离远还是通过父亲节点的距离远。


#include<cstdio>
#include<iostream>
#include<cmath>
#include<cctype>
#include<cstring>

#define ll long long
#define mst(a,b) memset(a,b,sizeof(a))

using namespace std;

struct edge{
    int next, to,val;
}z[10005];

int cnt, n,  dp[10005][3], head[10005], a[10005], b[10005];

inline int read( )
{
    int x = 0; char c = getchar( );
    while(!isdigit(c)) c = getchar( );
    while(isdigit(c)) x = (x<<1) + (x<<3) + (c^48), c = getchar( );
    return 0;
}

void add(int x,int y,int v)
{
    z[++cnt].next = head[x];
    z[cnt].to = y;
    z[cnt].val = v;
    head[x] = cnt;
}

void dfs1(int x)
{
    for(int i = head[x]; i ;i = z[i].next)
    {
        int y = z[i].to;
        dfs1(y);
        int w =z[i].val;
        int temp = dp[y][0] + w;
        if(temp >= dp[x][0])
        {
            dp[x][1] = dp[x][0];
            dp[x][0] = temp;
        }
        else
            if(temp > dp[x][1])
            {
                dp[x][1] = temp;
            }
    }
}

void dfs2(int x)
{
    for(int i = head[x]; i ; i = z[i].next)
    {
        int y = z[i].to;
        if(dp[x][0] == dp[y][0] + z[i].val)
        {
            dp[y][2] = max(dp[x][2] , dp[x][1]) + z[i].val;
        }
        else
        {
            dp[y][2] = max(dp[x][2] , dp[x][0]) + z[i].val;
        }
        dfs2(y);
    }
}

int main( )
{
    while(~scanf("%d",&n))
    {
        mst(head, 0);
        cnt = 0;
        for(int i = 2; i <= n; i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            add(a, i, b);
        }
        mst(dp, 0);
        dfs1(1);
        dfs2(1);
        for(int i = 1; i <= n; i++)
            printf("%d\n",max(dp[i][0], dp[i][2]));
    }
    return 0;
}

添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值