目录
- 引言
- 一、没有上司的舞会
- 二、树的重心
- 三、树的最长路径
- 四、树的中心
引言
关于这个树形 D P DP DP 代码其实都是那一套,核心还是在于思维上的难度,关键是这个思路你能不能想明白,想明白了就非常的简单,因为代码几乎长得都差不多,就是某些地方的改变罢了。刚开始学还是很难的,尤其是这种东西还会跟一些其它的算法混在一起,就很讨厌了,所以继续加油吧!
一、没有上司的舞会
标签:动态规划、树形DP、状态机模型
思路:
这道题其实写了很多遍了,就是定义一个状态 f [ i ] [ j ] , ( j = 0 , 1 ) f[i][j],(j=0,1) f[i][j],(j=0,1) ,代表选/不选当前结点 i i i 的情况下,整个分支的最大值。那么状态转移方程为: f [ u ] [ 0 ] = f [ u ] [ 0 ] + ∑ m a x ( f [ j ] [ 0 ] , f [ j ] [ 1 ] ) f[u][0] = f[u][0] + \sum max(f[j][0],f[j][1]) f[u][0]=f[u][0]+∑max(f[j][0],f[j][1]) f [ u ] [ 1 ] = f [ u ] [ 1 ] + ∑ f [ j ] [ 0 ] f[u][1] = f[u][1] + \sum f[j][0] f[u][1]=f[u][1]+∑f[j][0] 然后就进行递归操作即可,本质上还是先递归到子节点,然后子节点推出根结点的一个过程。
题目描述:
Ural 大学有 N 名职员,编号为 1∼N。他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。输入格式
第一行一个整数 N。接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。接下来 N−1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。(注意一下,后一个数是前一个数的父节点,不要搞反)。输出格式
输出最大的快乐指数。数据范围
1≤N≤6000,−128≤Hi≤127
输入样例:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出样例:
5
示例代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 6010, M = N, INF = 0x3f3f3f3f;int n, m;
int w[N];
int h[N], e[M], ne[M], idx;
int f[N][2];
bool has_father[N];void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}void dfs(int u)
{f[u][1] = w[u];for(int i = h[u]; i != -1; i = ne[i]){int j = e[i];dfs(j);f[u][0] += max(f[j][0], f[j][1]);f[u][1] += f[j][0];}
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);memset(h, -1, sizeof h);cin >> n;for(int i = 1; i <= n; ++i) cin >> w[i];for(int i = 0; i < n - 1; ++i){int a, b; cin >> a >> b;add(b,a);has_father[a] = true;}int root = 1;while(has_father[root]) root++;dfs(root);cout << max(f[root][0], f[root][1]) << endl;return 0;
}
二、树的重心
标签:dfs、树形DP
思路:
虽然从代码上感觉不出来这是个 D P DP DP ,但其实 D P DP DP 就是用一个状态表示了很多的状态就叫做 D P DP DP ,这道题就是用了一个 t t t 代表包括它在内的向下子树的结点的个数,然后在每个结点中都可以求出这个值,然后求出最大的,然后向上的连通块的个数拿总的结点数减去分支的总和即可,然后找出最小的最大值即可。这里值得注意的是,没有告诉父子关系,所以我们得建无向边,同时要防止向上递归回去,这里可以用两种方式解决,第一种是在参数中传一个参数,是该结点的父亲节点,遍历分支的时候判断一下即可,第二种是用一个判重数组,访问过就不访问了,这两种都可以。
题目描述:
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。输入格式
第一行包含整数 n,表示树的结点数。接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。数据范围
1≤n≤105
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4
示例代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 1e5+10, M = N * 2, INF = 0x3f3f3f3f;int n, m;
int h[N], e[M], ne[M], idx;
bool st[N];
int ans = 2e9;void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}int dfs(int u)
{st[u] = true;int sum = 1, size = 0;for(int i = h[u]; i != -1; i = ne[i]){int j = e[i];if(st[j]) continue;int t = dfs(j);sum += t;size = max(size, t);}size = max(size, n - sum);ans = min(ans, size);return sum;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);memset(h, -1, sizeof h);cin >> n;for(int i = 0; i < n - 1; ++i){int a, b; cin >> a >> b;add(a,b), add(b,a);}dfs(1);cout << ans << endl;return 0;
}
三、树的最长路径
标签:树的深度优先遍历、DP、树形DP、树的直径
思路:
首先这道题要求的是树的直径,这道题跟 大臣的旅费 不同的是该题的权重不相同,且不为正整数。最初的想法就是求最长路,想着拿 s p f a spfa spfa 求,只要把判断 d i s t dist dist 的条件变了就行了,但是发现不对,因为 s p f a spfa spfa 会重复经过一个点,这个直径每个点只会经过一次,然后就是用 D i j k s t r a Dijkstra Dijkstra 求的话,好像它的性质也不适用于最长路,然后就只能用树形 D P DP DP 来求了。然后就是整体的思路就是递归每一个点,递归的值为该点向下的最大值,然后我们求出每个点多个分支中的第一、第二大点的和,然后对每个点的值求最值就是所求的答案。之所以说这道题为树形 D P DP DP ,还是因为这个 t t t 代表着是一个集合的一个最值,这是 D P DP DP 的核心。
注意的点:第一、第二大值初始化应为 0 0 0 ,因为代表着无边,如果是负的,那就不选该边,也比它大,所以这个值最小也是 0 0 0 ,代表着什么边也不选。并且因为只能向下走,跟上道题不同的是,这次使用的是第二种的判断方法。又因为不知道父子结点的关系,所以要建无向边,防止不能递归所有的点。
题目描述:
给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。现在请你找到树中的一条最长路径。换句话说,要找到一条路径,使得使得路径两端的点的距离最远。注意:路径中可以只包含一个点。输入格式
第一行包含整数 n。接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。输出格式
输出一个整数,表示树的最长路径的长度。数据范围
1≤n≤10000,1≤ai,bi≤n,−105≤ci≤105
输入样例:
6
5 1 6
1 4 5
6 3 9
2 6 8
6 1 7
输出样例:
22
示例代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 1e4+10, M = N * 2, INF = 0x3f3f3f3f;int n, m;
int h[N], e[M], w[M], ne[M], idx;
int ans = -2e9;void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}int dfs(int u, int father)
{int d1 = 0, d2 = 0; // 最次也是0,没有路径for(int i = h[u]; i != -1; i = ne[i]){int j = e[i];if(j == father) continue;int t = dfs(j,u) + w[i];if(t >= d1) d2 = d1, d1 = t;else if(t > d2) d2 = t; } ans = max(ans, d1 + d2);return d1;
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);memset(h, -1, sizeof h);cin >> n;for(int i = 0; i < n - 1; ++i){int a, b, c; cin >> a >> b >> c;add(a,b,c), add(b,a,c); } dfs(1,-1);cout << ans << endl;return 0;
}
四、树的中心
标签:树的深度优先遍历、DP、树形DP、换根DP
思路:
这道题其实跟上一道题非常的像,题目要求的就是所有结点到其它结点最近的最远距离是多少,首先我们可以跟上一道题的思路一样求出每个点向下走的最远距离,可以用一个数组存起来,然后就是求其向上走的最远距离了,然后两个取最大值,最后每个点取最小的最大值即可。前一个就不说了,就是上一题的思路,然后是求向上走的最大距离,向上走的点要么继续向上走,要么向下走,向上走就是用父结点来更新子结点,我觉得这时候的上和下就刚好反过来了,因为本来这个结点向下走和从父结点到子结点,然后子结点的向上走就是该连边加上父节点向下走的距离或者是父节点向上走的距离,只不过如果父结点向下走的最长距离中的结点有子结点,那就用第二长边即可,详情见代码。
题目描述:
给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。输入格式
第一行包含整数 n。接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。输出格式
输出一个整数,表示所求点到树中其他结点的最远距离。数据范围
1≤n≤10000,1≤ai,bi≤n,1≤ci≤105
输入样例:
5
2 1 1
3 2 1
4 3 1
5 1 1
输出样例:
2
示例代码:
#include <bits/stdc++.h>using namespace std;typedef long long LL;
typedef pair<int,int> PII;
#define x first
#define y secondconst int N = 1e4+10, M = N * 2, INF = 0x3f3f3f3f;int n, m;
int h[N], e[M], w[M], ne[M], idx;
int d1[N], d2[N], p1[N], up[N];
bool is_leaf[N];void add(int a, int b, int c)
{e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}int dfs_d(int u, int father)
{d1[u] = d2[u] = -INF;for(int i = h[u]; i != -1; i = ne[i]){int j = e[i];if(j == father) continue;int d = dfs_d(j,u) + w[i];if(d >= d1[u]){d2[u] = d1[u], d1[u] = d;p1[u] = j;}else if(d > d2[u]){d2[u] = d;}}if(d1[u] == -INF){d1[u] = d2[u] = 0;is_leaf[u] = true;}return d1[u];
}void dfs_u(int u, int father)
{for(int i = h[u]; i != -1; i = ne[i]){int j = e[i];if(j == father) continue;if(j == p1[u]) up[j] = max(up[u], d2[u]) + w[i];else up[j] = max(up[u], d1[u]) + w[i];dfs_u(j,u);}
}int main()
{ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);memset(h, -1, sizeof h);cin >> n;for(int i = 0; i < n - 1; ++i){int a, b, c; cin >> a >> b >> c;add(a,b,c), add(b,a,c);}dfs_d(1,-1);dfs_u(1,-1);int res = d1[1];for(int i = 2; i <= n; ++i){if(is_leaf[i]) res = min(res, up[i]);else res = min(res, max(d1[i], up[i]));}cout << res << endl;return 0;
}