2022-08-03 21:42阅读: 149评论: 0推荐: 0

树形DP

树形 DP,即在树上进行的 DP。
由于树固有的递归性质,树形 DP 一般都是递归进行的。

树的最长路径

题目描述
给定一个含有 n 个节点的 树,以及树中每条边的权值 wedgei。
现需要在树中找出一条路径,使得该路径上所有边的权值之和最大。

思路:
记录以i为根节点的子树中,从子树某个节点到i的最长路和次长路。
那么以经过i的最长边就是最长路+次长路。
而且很明显可以递归求解。

闫氏DP分析法
状态表示—集合f1,i,f2,i: 以节点 i 为根的子树中,从子树某个节点到 i 的最长路为 f1,次长路为 f2
状态表示—属性: 路径长度的最大值 Max
状态计算.......
代码:

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define pll pair<int, int>
#define ull unsigned long long
using namespace std;
const int N = 10010, M = N * 2;
int n;
int to[M], w[M], pre[M], h[N], idx;
void add(int a, int b, int c)
{
to[idx] = b, w[idx] = c, pre[idx] = h[a], h[a] = idx++;
}
int ans = 0;
int dfs(int u, int fa)
{
int d1 = 0, d2 = 0;
for (int i = h[u]; i != -1; i = pre[i])
{
int j = to[i];
if (j == fa)
continue;
int d = dfs(j, u)+w[i];
if (d >= d1)
d2 = d1, d1 = d;
else if (d > d2)
d2 = d;
}
ans = max(ans, d1 + d2);
return d1;
}
void solve()
{
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;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}

应用

数字转换

题意:
如果一个数 x 的约数之和 y(不包括他本身)比他本身小,那么 x 可以变成 y,y 也可以变成 x。

例如,4的约数有1,2。因此 4可以变为 3,3 可以变为 4。

限定所有数字变换在不超过 n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。

思路:

本题的重点在建图:如何分析出题目是一棵树或者是一个森林。

根据题意,单看一个数字 x,那么他能转换到的数有:

  1. x 的约数之和 xx<x
  2. 所有大于 x,且约数之和等于 x 的数 xi

又因为每个数x 最多只能有比他小的x,所以每个数只能向比他小的数字建立一个边,所以一定是个森林。

森林: 每一个 点,至多只有一条 入边

建好图后,本题就 等价于 找出一个森林中,直径最长 的树,并求出该树的 直径

如何建图呢有两种方法
第一种就是暴力,暴力每个数的约数是谁,求出约数和。

另一种是看这个数能组成哪些数(求他是谁的约数)
然后因为 1需要乘n次,2需要乘n/2次,3需要乘n/3次....所以复杂度是O(nlogn)

代码:

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define pll pair<int, int>
#define ull unsigned long long
using namespace std;
const int N = 50000, M = N * 2;
int n;
int pre[M], to[M], h[N], idx;
int fsum[N];
void add(int a, int b)
{
to[idx] = b, pre[idx] = h[a], h[a] = idx++;
}
int ans = 0;
bool vis[N];
int dfs(int u)
{
vis[u] = true;
int d1 = 0, d2 = 0;
for (int i = h[u]; i != -1; i = pre[i])
{
int j = to[i];
int d = dfs(j);
if (d > d1)
d2 = d1, d1 = d;
else if (d > d2)
d2 = d;
}
ans = max(ans, d1 + d2);
return d1 + 1;
}
void solve()
{
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 2; i * j <= n; j++)
{
fsum[i * j] += i;
}
}
for (int i = 2; i <= n; i++)
{
if (fsum[i] < i)
add(fsum[i], i);
}
for (int i = 1; i <= n; i++)
if (!vis[i])
dfs(i);
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}

树的中心 换根DP

题意:
给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。
请你在树中找到一个点,使得该点到树中其他结点的最远距离 最近

思路:
思考一下:求一个节点的 最远距离 时,会有几种路径呢:

  1. 从当前节点往下,直到子树中某个节点的最长路径

  2. 从当前节点往上走到其父节点,再从其父节点出发且不回到该节点的最长路径

因此我们可以先 dfs 一遍,预处理出当前子树对于根的最大贡献(距离)和 次大贡献(距离)

处理 次大贡献(距离) 的原因是:
如果 当前节点 是其 父节点子树 的 最大路径 上的点。此时 父节点子树 的 最大贡献 不能算作对该节点的贡献,就要使用次大贡献

因为我们的路径是 简单路径(不能 走回头路)

然后我们再 dfs 一遍,求解出每个节点的父节点对他的贡献(即每个节点往上能到的最远路径

两者比较,取一个 max 即可
时间复杂度: T(2n)=O(n)

到这里就可以总结出换根DP 的思想了:

换根DP 一般分为三个步骤:

  1. 指定任意一个根节点

  2. 一次dfs遍历,统计出当前子树内的节点对当前节点的贡献

  3. 一次dfs遍历,统计出当前节点的父节点对当前节点的贡献,然后合并统计答案

代码:

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define pll pair<int, int>
#define ull unsigned long long
using namespace std;
const int N = 10010, M = N * 2;
int n;
int to[M], w[M], pre[M], h[N], idx;
int d1[N], d2[N];
int fa1[N];
int up[N];
void add(int a, int b, int c)
{
to[idx] = b, w[idx] = c, pre[idx] = h[a], h[a] = idx++;
}
int dfs_d(int u, int father)
{
for (int i = h[u]; i != -1; i = pre[i])
{
int j = to[i];
if (j == father)
continue;
int d = dfs_d(j, u) + w[i];
if (d > d1[u])
{
d2[u] = d1[u], d1[u] = d;
fa1[u] = j;
}
else if (d > d2[u])
{
d2[u] = d;
}
}
return d1[u];
}
void dfs_u(int u, int father)
{
for (int i = h[u]; i != -1; i = pre[i])
{
int j = to[i];
if (j == father)
continue;
if (fa1[u] == j)
up[j] = max(up[u], d2[u]) + w[i];
else
up[j] = max(up[u], d1[u]) + w[i];
dfs_u(j, u);
}
}
void solve()
{
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 ans = 0x3f3f3f3f3f;
for (int i = 1; i <= n; i++)
ans = min(ans, max(up[i], d1[i]));
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}

主要参考:一只野生彩色铅笔
https://www.acwing.com/user/myspace/index/55909/

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16548828.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(149)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起