好两天没写博客了, 原因是这两天我找出了我的老版的机械的键盘, 想试一下机械键盘打字的速度, 但是怎么感觉好不习惯呐,于是练了两天的打字速度, 速度稍微上来点了,最起码打字字母差不多能按对了, 有点累, 这两天的代码量最起码也得2000行+, 键盘还是黑轴的,受累的不行,有的时候一直按不对就会很气!真的很气,但是有天早晨我打开电脑, 看到电脑锁屏图片上有这一行字, “每当你觉得累的时候, 都是里终点不远的时候”, 我静下心来想了一下, 好像也是哦,练习打字速度也需要很多耐心吧, 很多人坚持不下来呢, 尤其是新手, 想想自己接触编程语言也接近一年了, 达到了这样的水平也不枉我付出了这些吧。
练习完打字赶紧回去学算法, 还好都还没忘, 看了看之前写的几道题目,写了几道,回想思路的时候又想到了许多新的思路和方法, 果然好的题目还是要多做几遍呢。其中发现了一种很简单的题型, 平常我都不看在眼里的题型(02小姐姐说不能自大, 我要牢记在心),就是关于dfs遍历树的一种题型的加强版之树形dp之背包问题, 我发现有的题目是边上有权值, 有的是点上有权值, 关于点上有权值的就比较简单了,直接用有依赖问题背包解决, 关键是边上的权值怎么搞。我在看我之前写的代码的时候, 发现我还没有真正的明白里边的过程,于是想写一篇博客来记录一下。
举个栗子:
有一棵二叉苹果树,如果树枝有分叉,一定是分两叉,即没有只有一个儿子的节点。
这棵树共 N 个节点,编号为 1 至 N,树根编号一定为 1。
我们用一根树枝两端连接的节点编号描述一根树枝的位置。
一棵苹果树的树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果。
这里的保留是指最终与1号点连通。
输入格式
第一行包含两个整数 N 和 Q,分别表示树的节点数以及要保留的树枝数量。
接下来 N−1 行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。
输出格式
输出仅一行,表示最多能留住的苹果的数量。
数据范围
1
≤
Q
<
N
≤
100.
1≤Q<N≤100.
1≤Q<N≤100.
N
≠
1
,
N≠1,
N=1,
每根树枝上苹果不超过 30000 个。
输入样例:
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出样例:
21
其实当然可以将所有的边上的节点都归结到子节点上去如果是这样的话, 那么代码就变成了:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, M = 220;
int h[N], e[M], ne[M], idx, w[M], n, m;
int f[N][N];
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void dfs(int x, int wh, int father)
{
for(int i = 1; i <= m + 1; i ++) f[x][i] = wh;
for(int i = h[x]; i != -1; i = ne[i])
{
int k = e[i];
if(k == father) continue;
dfs(k, w[i], x);
for(int j = m + 1; j > 1; j --)
{
for(int r = 0; r < j; r ++)
{
f[x][j] = max(f[x][j], f[x][j - r] + f[k][r]);
}
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++)
{
int a, b, w; cin >> a >> b >> w;
add(a, b, w), add(b, a, w);
}
dfs(1, 0, -1); // 根节点的权值是0
//for(int i = 1; i <= m + 1; i ++) cout << f[3][i] << endl;
cout << f[1][m + 1] << endl; // 由于是点按照点来遍历, 所以要写 m + 1, 算是上根节点,
return 0;
}
emmm看起来代码非常的冗杂↑, 这是我非常不喜欢的,谁又不喜欢简单可读性高的代码呢(02小姐姐也喜欢好看的代码,所以我要写好看的代码)↓
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210;
int f[N][N], h[N], e[N], ne[N], w[N], n, m, idx;
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void dfs(int x, int father)
{
for (int i = h[x]; i != -1; i = ne[i])//
{
if(e[i] == father) continue;
dfs(e[i], x);
for (int j = m; j >= 1; j --)
{
for (int k = 0; k < j; k ++)
{
f[x][j] = max(f[x][j], f[x][j - k - 1] + f[e[i]][k] + w[i]);
}
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i < n; i ++)
{
int a, b, c; cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
cout << f[1][m] << endl;
return 0;
}
这是另一份代码,比我写的代码要简单很多, 于是我就要开始研究里边的思路了, 思路的的话也很简单, 只要明白状态表示的含义就差不多能明白了, 表示的含义就是直来直去的f[i][j]
表示以i
为根的点总共有有j
条边连接i
的最大权值。
困扰了我不少时间, 因为我一直想用点上带权值的思路去理解这份代码, 有时候转换一下思路也很重要呢。