[DP总结]树形DP

树形DP

树形DP,顾名思义,是在树上进行DP操作,因此往往需要使用DP实现,在转移时,通常先递归求出子树中的解,再在根节点进行计算转移。

树中只有两种关系,一种是父子关系,一种是平行关系。

下面是几道简单的树形DP。从中我们可以窥出树形DP的本质。

[luogu] P1352 没有上司的舞会

题目描述

某大学有N个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

输入输出格式

输入格式:

第一行一个整数N。(1<=N<=6000)

接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)

接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。

最后一行输入0 0

输出格式:

输出最大的快乐指数。

输入输出样例

输入样例#1:

7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0

输出样例#1:

5

最经典的一道题之一。考虑这道题中每个人之间的关系,可以发现每个人的上司可以看做是他的父节点,并且呈树状分布,显然是树形DP的思路。由于如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会,那么我们定义\(dp[u][0/1]\)表示当前选(1)或不选(0)的最大可行值。那么DP转移方程显而易见:\(dp[u][0]+=max(dp[v][0] + dp[v][1])\)和\(dp[u][1]+=dp[v][0]\)其中\(v \in u.to\)

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;

const int MAXN = 6010;
int n, w[MAXN];
int fa[MAXN], dp[MAXN][2];

struct Edge {
    int nxt, to;
}e[MAXN];

template <typename _Tp>
inline void read(_Tp &x) {
    char ch = getchar( ); bool f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar( ); }
    while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar( ); }
    x *= f;
}

int head[MAXN], tot;
inline void AddEdge(int x, int y) {
    e[++ tot].to = y, e[tot].nxt = head[x], head[x] = tot;
}

int Dfs(int u) {
    dp[u][0] = 0, dp[u][1] = w[u];
    for (int i = head[u], v; i; i = e[i].nxt) {
        v = e[i].to;
        Dfs(v);
        dp[u][0] += max(dp[v][0], dp[v][1]);
        dp[u][1] += dp[v][0];
    }
}

int main( ) {
    read(n);
    for (int i = 1; i <= n; ++ i) read(w[i]);
    int L, K;
    for (int i = 1; i < n; ++ i) {
        read(L), read(K);
        AddEdge(K, L);
        fa[L] = K;
    }
    //寻找根节点
    int rt = 1;
    while (fa[rt]) rt = fa[rt];
    Dfs(rt);
    printf("%d\n", max(dp[rt][0], dp[rt][1]));
    return 0;
}

上面的代码运行了29ms,不快不慢,标准树形DP。

还有一种做法,可以达到0ms,时间复杂度直逼\(O(n)\)输入下界。代码是这样的:

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;

const int MAXN = 6010;
int n, w[MAXN];
int dp[MAXN][2];

template <typename _Tp>
inline void read(_Tp &x) {
    char ch = getchar( ); bool f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar( ); }
    while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar( ); }
    x *= f;
}

int main( ) {
    int ans = 0;
    read(n);
    for (int i = 1; i <= n; ++ i) read(dp[i][1]);
    int u, v;
    for (int i = 1; i < n; ++ i) {
        scanf("%d%d", &v, &u);
        dp[u][1] += dp[v][0];
        dp[u][0] += max(dp[v][0], dp[v][1]);
        ans = max(ans, max(dp[u][0], dp[u][1]));
    }
    printf("%d\n", ans);
    return 0;
}

上述代码直接抛弃了显性的树形结构,对于每个输入的父子节点,在输入时直接用子节点更新父节点的最优值,利用了线性DP的思想。由此可见,树形DP是很灵活的,太过拘泥于树有时反而想不到最优解法。

但是相比较而言,树形递归要比递推的思路更加显然,更加清晰,因此在做一些比较复杂,不好递推的题目时,递归Dfs显然是必须的。

[luogu] P2014 选课

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b)。一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?

输入输出格式

输入格式:

第一行有两个整数N,M用空格隔开。(1<=N<=300,1<=M<=300)

接下来的N行,第I+1行包含两个整数ki和si, ki表示第I门课的直接先修课,si表示第I门课的学分。若ki=0表示没有直接先修课(1<=ki<=N, 1<=si<=20)。

输出格式:

只有一行,选M门课程的最大得分。

输入输出样例

输入样例#1:

7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2

输出样例#1:

13

还是显然的树形DP。首先要处理的问题是,关于本题的关系结构。细心的同学可能会发现,题目中并没有保证这是一棵树,准确来说,本题的关系是一个森林。对于这种结构,我们通常增加一个虚拟节点0,并从0向各个不连通的树的根连一条边,使之成为一颗真正的多叉树。题目中\(k_i=0\)表示没有直接先修课这个性质实际上简化了这一问题,这样只需正常读入即可保证是一棵根节点为0的树。

考虑\(dp[i][j]\)表示以\(i\)为根,选了\(j\)个节点的最大值。那么转移方程就是\(dp[i][j]=max(dp[v][k] + dp[i][j-k-1]+s[v])\)。表示以\(i\)为根,选了\(j-k-1\)个节点之后,由于要从他的儿子中选节点他自己本身必须选,所以先选上\(v\)节点本身,再从他的一个儿子中选取\(k\)个节点。答案是\(dp[0][m]\)

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;

const int MAXN = 1010;
int n, m, s[MAXN];
int dp[MAXN][MAXN];

struct Edge {
    int nxt, to;
}e[MAXN];

template <typename _Tp>
inline void read(_Tp &x) {
    char ch = getchar( ); _Tp f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar( ); }
    while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar( ); }
    x *= f;
}

int head[MAXN], tot;
inline void AddEdge(int x, int y) {
    e[++ tot].to = y, e[tot].nxt = head[x], head[x] = tot;
}

void Dfs(int u) {
    for (int i = head[u], v; i; i = e[i].nxt) {
        v = e[i].to;
        Dfs(v);
        for (int j = m; j; -- j)
            for (int k = j - 1; k >= 0; -- k)
                dp[u][j] = max(dp[u][j], dp[v][k] + dp[u][j - k - 1] + s[v]);
    }
}

int main( ) {
    read(n), read(m);
    int fa;
    for (int i = 1; i <= n; ++ i) {
        read(fa);
        read(s[i]);
        AddEdge(fa, i);
    }
    Dfs(0);
    printf("%d\n", dp[0][m]);
    return 0;
}

观察这个转移方程,我们发现,它与分组背包十分类似。事实上,这道题就是一个经典的树上分组背包模型。

[luogu] P2015 二叉苹果树

题目描述

有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)

这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。

我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树

2   5
 \ /
  3   4
   \ /
    1

现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

给定需要保留的树枝数量,求出最多能留住多少苹果。

输入输出格式

输入格式:

第1行2个数,N和Q(1<=Q<= N,1<N<=100)。

N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。

每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。

每根树枝上的苹果不超过30000个。

输出格式:

一个数,最多能留住的苹果的数量。

输入输出样例

输入样例#1:

5 2
1 3 1
1 4 10
2 3 20
3 5 20

输出样例#1:

21

观察到本题与上一题十分类似,稍微不同的是,上一题是多叉树,本题是二叉树,这对DP本身没有什么实质性的影响。注意题目中说道“一些树枝上长有苹果”,也就是说苹果数量并不是点权而是边权,因此本题的状态转移与上一题有细微的差别(将点权变为边权)。还有一点不同是,题目中的读入数据并没有给定明确的父子关系,因此如何建树是一个问题。这种情况下最常见的方法是将边建成双向边。每次Dfs时,判断它是否往回指向了父节点,如果是直接跳过,不是则表明当前节点就是子节点。

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;

const int MAXN = 1010;
int N, Q;
int dp[MAXN][MAXN];

struct Edge {
    int nxt, to, val;
}e[MAXN << 1];

template <typename _Tp>
inline void read(_Tp &x) {
    char ch = getchar( ); _Tp f = 1; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar( ); }
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar( );
    x *= f;
}

int head[MAXN], tot;
void AddEdge(int x, int y, int z) {
    e[++ tot].to = y, e[tot].val = z, e[tot].nxt = head[x], head[x] = tot;
}

void Dfs(int u, int fa) {
    for (int i = head[u], v; ~i; i = e[i].nxt) {
        v = e[i].to;
        if (v == fa) continue;
        Dfs(v, u);
        for (int j = Q; j; -- j)
            for (int k = j - 1; k >= 0; -- k)
                dp[u][j] = max(dp[u][j], dp[v][k] + dp[u][j - k - 1] + e[i].val);
    }
}

int main( ) {
    memset(head, -1, sizeof(head));
    read(N), read(Q);
    int x, y, z;
    for (int i = 1; i < N; ++ i) {
        read(x), read(y), read(z);
        AddEdge(x, y, z);
        AddEdge(y, x, z);
    }
    Dfs(1, 0);
    printf("%d\n", dp[1][Q]);
    return 0;
}

实际上可以加一个小优化,增加一个数组\(cntE[i]\)表示以\(i\)为根的树的边数,那么转移为

void Dfs(int u, int fa) {
    for (int i = head[u], v; ~i; i = e[i].nxt) {
        v = e[i].to;
        if (v == fa) continue;
        Dfs(v, u);
        cntE[u] += cntE[v] + 1;
        for (int j = min(Q, cntE[u]); j; -- j)
            for (int k = min(j - 1, cntE[v]); k >= 0; -- k)
                dp[u][j] = max(dp[u][j], dp[v][k] + dp[u][j - k - 1] + e[i].val);
    }
}

然而并没有什么卵用

[luogu] P1273 有线电视网

题目描述

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。

从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。

现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。

写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。

输入输出格式

输入格式:

输入文件的第一行包含两个用空格隔开的整数N和M,其中2≤N≤3000,1≤M≤N-1,N为整个有线电视网的结点总数,M为用户终端的数量。

第一个转播站即树的根结点编号为1,其他的转播站编号为2到N-M,用户终端编号为N-M+1到N。

接下来的N-M行每行表示—个转播站的数据,第i+1行表示第i个转播站的数据,其格式如下:

K A1 C1 A2 C2 … Ak Ck

K表示该转播站下接K个结点(转播站或用户),每个结点对应一对整数A与C,A表示结点编号,C表示从当前转播站传输信号到结点A的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。

输出格式:

输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。

输入输出样例

输入样例#1:

5 3
2 2 2 5 3
2 3 2 4 3
3 4 2

输出样例#1:

2

说明

样例解释

如图所示,共有五个结点。结点①为根结点,即现场直播站,②为一个中转站,③④⑤为用户端,共M个,编号从N-M+1到N,他们为观看比赛分别准备的钱数为3、4、2,从结点①可以传送信号到结点②,费用为2,也可以传送信号到结点⑤,费用为3(第二行数据所示),从结点②可以传输信号到结点③,费用为2。也可传输信号到结点④,费用为3(第三行数据所示),如果要让所有用户(③④⑤)都能看上比赛,则信号传输的总费用为:

2+3+2+3=10,大于用户愿意支付的总费用3+4+2=9,有线电视网就亏本了,而只让③④两个用户看比赛就不亏本了。

一眼题……依然是个树上依赖分组背包。\(dp[i][j]\)表示以\(i\)为根节点,选了\(j\)个终端的最大收益。

本题的不同之处在于,选就一定要选到根节点(用户端),不然显然亏了。

鉴于本题的特殊性,Dfs写成int类型比较好。

(我保证下一题一定不是树上分组背包了)

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;

const int MAXN = 3010;
int N, M;
int f[MAXN][MAXN], W[MAXN];

struct Edge {
    int nxt, to, val;
}e[MAXN];

template <typename _Tp>
inline void read(_Tp &x) {
    char ch = getchar( ); _Tp f = 1; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar( ); }
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar( );
    x *= f;
}

int head[MAXN], tot;
void AddEdge(int x, int y, int z) {
    e[++ tot].to = y, e[tot].val = z, e[tot].nxt = head[x], head[x] = tot;
}

int Dfs(int u) {
    if (u > N - M) {
        f[u][1] = W[u];
        return 1;
    }
    int sum = 0; //sum表示当前节点子树中共选了多少终端
    for (int i = head[u], v; ~i; i = e[i].nxt) {
        v = e[i].to;
        sum += Dfs(v);
        for (int j = sum; j; -- j)
            for (int k = sum; k; -- k)
                f[u][j] = max(f[u][j], f[v][k] + f[u][j - k] - e[i].val);
    }
    return sum;
}

int main( ) {
    memset(head, -1, sizeof(head));
    read(N), read(M);
    int x, y, k;
    for (int i = 1; i <= N - M; ++ i) {
        read(k);
        for (int j = 1; j <= k; ++ j) {
            read(x), read(y);
            AddEdge(i, x, y);
        }
    }
    for (int i = 1; i <= M; ++ i) read(W[N - M + i]);
    memset(f, -10, sizeof(f));
    for (int i = 1; i <= N; ++ i) f[i][0] = 0;
    Dfs(1);
    for (int i = M; i; -- i) if (f[1][i] >= 0) {
        printf("%d\n", i);
        break;
    }
    return 0;
}

一定要注意DP数组的初值要足够小啊QWQ

[luogu] P1270 “访问”美术馆

题目描述

经过数月的精心准备,Peer Brelstet,一个出了名的盗画者,准备开始他的下一个行动。艺术馆的结构,每条走廊要么分叉为两条走廊,要么通向一个展览室。Peer知道每个展室里藏画的数量,并且他精确测量了通过每条走廊的时间。由于经验老到,他拿下一幅画需要5秒的时间。你的任务是编一个程序,计算在警察赶来之前,他最多能偷到多少幅画。

输入输出格式

输入格式:

第1行是警察赶到的时间,以s为单位。第2行描述了艺术馆的结构,是一串非负整数,成对地出现:每一对的第一个数是走过一条走廊的时间,第2个数是它末端的藏画数量;如果第2个数是0,那么说明这条走廊分叉为两条另外的走廊。数据按照深度优先的次序给出,请看样例。

一个展室最多有20幅画。通过每个走廊的时间不超过20s。艺术馆最多有100个展室。警察赶到的时间在10min以内。

输出格式:

输出偷到的画的数量

输入输出样例

输入样例#1:

60
7 0 8 0 3 1 14 2 10 0 12 4 6 2

输出样例#1:

2

这题就比较有意思了。首先输入就比较有意思……仔细分析发现可以递归读入当前节点的左儿子右儿子,将整张图抽象成一棵树。显然每条走廊来一次,回一次,一共要走两次,所以时间要乘2。如果当前节点有画可偷,直接考虑消耗\(i\)的时间可以投多少画,也就是\(dp[u][i]=min((i - t[u].t) / 5, t[u].p)\)。(别忘了最多只能偷\(t[u].p\)张画)。没画可偷,就意味着有一个分叉道路,也就是需要递归左右儿子节点,然后做一次背包即可。所以这是一道经典树形背包问题。

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
#define ls (u << 1)
#define rs (u << 1 | 1)
using namespace std;

const int MAXN = 1010;
int S;
int dp[MAXN][MAXN];

struct Tree {
    int t, p;
}t[MAXN];

template <typename _Tp>
inline _Tp read(_Tp &x) {
    char ch = getchar( ); _Tp f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar( ); }
    while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar( ); }
    return x * f;
}

void Init(int u) {
    scanf("%d%d", &t[u].t, &t[u].p);
    t[u].t <<= 1;
    if (!t[u].p) Init(ls), Init(rs);
}

void Dfs(int u) {
    if (t[u].p) {
        for (int i = t[u].t; i < S; ++ i)
            dp[u][i] = min((i - t[u].t) / 5, t[u].p);
        return ;
    }
    Dfs(ls), Dfs(rs);
    for (int i = 1; i < S; ++ i)
        for (int j = 0; j <= i - t[u].t; ++ j)
            dp[u][i] = max(dp[u][i], dp[ls][j] + dp[rs][i - t[u].t - j]);
}

int main( ) {
    read(S);
    Init(1);
    Dfs(1);
    printf("%d\n", dp[1][S - 1]);
    return 0;
}

原文地址:https://www.cnblogs.com/hkttg/p/9829281.html

时间: 2024-10-10 03:45:19

[DP总结]树形DP的相关文章

【DP】树形DP 记忆化搜索

DP中的树形DP,解决方法往往是记忆化搜索.显然,树上递推是很困难的.当然做得时候还是得把状态定义和转移方程写出来:dp[u][1/0]表示以u为根节点的树 涂(1) 或 不涂(0) 颜色的最少方案数.树上DP有两个经典问法:一条边两端至少有个一个端点涂色,问整个tree最少涂色次数:还有一种忘了...此题是前种问法. #include<cstdio> #include<cstring> #include<algorithm> using namespace std;

LuoguP5024 保卫王国(动态DP,树形DP,矩阵加速,LCT)

最小权覆盖集 = 全集 - 最大权独立集 强制取点.不取点可以使用把权值改成正无穷或负无穷实现 接下来就是经典的"动态最大权独立集"了 O(nlogn). 这不是我说的,是immortalCO大佬说的 于是我调了一万年极值,终在\(\frac{LLONG\_MAX}{3}\)时\(11s\)卡过... 知道最小权覆盖集 = 全集 - 最大权独立集,就是模板\(DDP\)了 #include <cstdio> #include <iostream> #includ

【转】【DP_树形DP专辑】【9月9最新更新】【from zeroclock&#39;s blog】

树,一种十分优美的数据结构,因为它本身就具有的递归性,所以它和子树见能相互传递很多信息,还因为它作为被限制的图在上面可进行的操作更多,所以各种用于不同地方的树都出现了,二叉树.三叉树.静态搜索树.AVL树,线段树.SPLAY树,后缀树等等.. 枚举那么多种数据结构只是想说树方面的内容相当多,本专辑只针对在树上的动态规划,即树形DP.做树形DP一般步骤是先将树转换为有根树,然后在树上进行深搜操作,从子节点或子树中返回信息层层往上更新至根节点.这里面的关键就是返回的信息部分,这个也没一般性的东西可讲

【BZOJ2286】【SDOI2011】消耗战 LCA单调性(构建虚树)+树形DP

题解: 首先我们考虑每次都做一遍树形DP(树形DP自己脑补去,随便乱搞就过了). 显然这是TLE无疑的. 所以可以利用LCA单调性构建虚树. 思想: 我们发现每次树形DP有很多点用不到,但是却需要被扫过,让他们见鬼去吧! 实现: 我们只对每次扫的图插入本次询问需要的节点,以及它们的LCA. 这样询问了m个点,虚树就至多只需要2m个点(so quick). 而插入顺序上不妨利用LCA单调性来把点按dfs度排个序,然后挨个插入单调栈. 同时我们要保证单调栈维护的是一条链,也就是一旦不是链了,我们自然

HDU-2196 Computer (树形DP)

最近在看树形DP,这题应该是树形DP的经典题了,写完以后还是有点感觉的.之后看了discuss可以用树分治来做,以后再试一试. 题目大意 找到带权树上离每个点的最远点.︿( ̄︶ ̄)︿ 题解: 对于每一个点的最远点,就是以这个点为根到所有叶子节点的最长距离.但是如果确定根的话,除了根节点外,只能找到每个节点(度数-1)个子树的最大值,剩下一个子树是该节点当前的父亲节点. 所以当前节点的最远点在当前节点子树的所有叶子节点以及父亲节点的最远点上(当父亲节点的最远点不在当前节点的子树上时), 如果父亲节

UVA-01220 Party at Hali-Bula (树形DP+map)

题目链接:https://vjudge.net/problem/UVA-1220 思路: 树形DP模板题,求最大人数很简单,难点在于如何判断最大人数的名单是否有不同的情况: 解决方法是用一个数组f[manx][2]记录该节点是否出场的情况,为真时代表有多种情况; 具体讨论: 当父节点的值加上某个子节点的值时,他的f的情况也和该子节点一样: 当某个节点dp(i, 0) == dp(i, 1), 则该节点以及它的父节点也一定有多种情况(父节点必定取其中之一). Code: 1 #include<bi

HDU 1520 树形dp裸题

1.HDU 1520  Anniversary party 2.总结:第一道树形dp,有点纠结 题意:公司聚会,员工与直接上司不能同时来,求最大权值和 #include<iostream> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<cstdio> #define max(a,b) a>b?a:b using nam

HDU2196 Computer(树形DP)

和LightOJ1257一样,之前我用了树分治写了.其实原来这题是道经典的树形DP,感觉这个DP不简单.. dp[0][u]表示以u为根的子树中的结点与u的最远距离 dp[1][u]表示以u为根的子树中的结点与u的次远距离 这两个可以一遍dfs通过儿子结点转移得到.显然dp[0][u]就是u的一个可能的答案,即u往下走的最远距离,还缺一部分就是u往上走的最远距离: dp[2][u]表示u往上走的最远距离 对于这个的转移,分两种情况,是这样的: dp[2][v] = max( dp[0][u]+w

hdu5593--ZYB&#39;s Tree(树形dp)

问题描述 ZYB有一颗N个节点的树,现在他希望你对于每一个点,求出离每个点距离不超过KK的点的个数. 两个点(x,y)在树上的距离定义为两个点树上最短路径经过的边数, 为了节约读入和输出的时间,我们采用如下方式进行读入输出: 读入:读入两个数A,B,令fai??为节点i的父亲,fa?1??=0;fa?i??=(A∗i+B)%(i−1)+1,i∈[2,N] . 输出:输出时只需输出N个点的答案的xor和即可. 输入描述 第一行一个整数TT表示数据组数. 接下来每组数据: 一行四个正整数N,K,A,