树形DP
AcWing 1072. 树的最长路径
#include <iostream>
#include <cstring>
const int N = 1e4 + 10, M = N << 1;//初始不确定树的拓扑结构,因此要建立双向边
int h[N], w[M], e[M], ne[M], idx;
int f[N];//表示以节点i为端点 到i节点的 最长路径
int ans = -1;
int n;
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 fa)
{
int d1 = 0, d2 = 0;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[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 = std::max(ans, d1 + d2);
return d1;
}
void solve()
{
std::cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; ++ i) //n - 1条边不是n条。。
{
int a, b, c;
std::cin >> a >> b >> c;
add(a,b, c), add(b, a, c);
}
dfs(1, -1);//我们可以任意选取一个点作为根节点,这样整棵树的拓扑结构被唯一确定下来了
std::cout << ans;
return ;
}
int main()
{
solve();
return 0;
}
AcWing 1073. 树的中心【父子节点相互传更新信息】
#include <iostream>
#include <cstring>
const int N = 1e4 + 10, M = N << 1, INF = 0x3f3f3f3f;
int d1[N], d2[N], up[N];
int p1[N];//记录最长路是从哪个子节点转移过来的
int h[N], e[M], ne[M], w[M], idx;
bool is_leaf[N];
int n;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dfs_down(int u, int fa)
{
d1[u] = d2[u] = -INF;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
int d = dfs_down(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;//不需要开一个p2[u]记录次短路径是从哪个点转移过来的,如果最长路径不行(经过j),那么次长路径肯定不经过j
//如果最长路径行(不经过j),那么也不用选择次长路径了
}
if (d1[u] == -INF)
{
d1[u] = d2[u] = 0;
is_leaf[u] = true;
}
return d1[u];
}
void dfs_up(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
if (p1[u] == j) up[j] = std::max(up[u], d2[u]) + w[i];//父节点更新子节点
else up[j] = std::max(up[u], d1[u]) + w[i];
dfs_up(j, u);
}
}
void solve()
{
memset(h, -1, sizeof h);
std::cin >> n;
for (int i = 0; i < n - 1; ++ i)
{
int a, b, c;
std::cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs_down(1, -1);//在n个点n-1条无向边的图中,单拎起任何一个点都可以是一颗树的根节点
dfs_up(1, -1);
int res = INF;
for (int i = 1; i <= n; ++ i)//遍历所有节点,
{
if (is_leaf[i]) res = std::min(res, up[i]);
else res = std::min(res, std::max(d1[i], up[i]));//不是叶子节点才能又向上又向下
}
std::cout << res ;
return ;
}
int main()
{
solve();
return 0;
}
AcWing 1075. 数字转换
#include <iostream>
#include <cstring>
using namespace std;
const int N = 5 * 1e4 + 10, INF = 0x3f3f3f3f;
int sum[N];
bool st[N];
int h[N], e[N], ne[N], idx;
int n;
int ans;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int dfs(int u) {
int d1 = 0, d2 = 0;//不能定义成-INF
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
int d = dfs(j) + 1;
if (d >= d1) d2 = d1, d1 = d;
else if (d >= d2) d2 = d;
}
// if (d1 = -INF) d1 = d2 = 0;
ans = max(ans, d1 + d2);
return d1;
}
int main()
{
std::cin >> n;
memset(h, -1, sizeof h);
//增加代码的扩展性(可以适应更大的数据范围)降低复杂度
for (int i = 1; i <= n; ++ i)//我们这里逆向思维,求i可以组成多少个数的约数,而不是试除法求约数(即求数i有哪些约数)
for (int j = 2; j <= n / i; ++ j)//因为题目说了约数和不包含自身,所以枚举的时候不能从1开始枚举
sum[i * j] += i;
for (int i = 2; i <= n; ++ i)
{
if (sum[i] < i)
{
add(sum[i], i);
st[i] = true;//我们转移的时候从小到大转移,将大的变成小的子节点,这样才能找到最长的
}
}
for (int i = 1; i <= n; ++ i)//有很多棵树,每棵树都有多种根节点,因此干脆把每个点都便利一遍
{
if (!st[i]) dfs(i);
}
std::cout << ans;
return 0;
}
AcWing 1074. 二叉苹果树
依赖背包问题,选择子节点必须选择它的父节点
#include <iostream>
#include <cstring>
const int N = 1e2 + 10, M = N << 1;//这里我不知道为什么要设计成无向图
int n, m;
int h[N], e[M], ne[M], w[M], idx;
int f[N][N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int ver = e[i];
//无向图,子节点会遍历到父节点
if (ver == fa) continue;
dfs(ver, u);//给当前的子节点ver赋值
for (int j = m; j >= 0; -- j)
for (int k = 0; k <= j - 1; ++ k)//给父节点到当前的节点留一条边
{
f[u][j] = std::max(f[u][j], f[u][j - k - 1] + f[ver][k] + w[i]);
}
}
}
int main()
{
std::cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; ++ i)//题目说了n-1行
{
int a, b, c;
std::cin >> a >> b >> c;
add(a, b, c), add(b, a, c);//不建两条边会wa,不知道为啥非要变成无向图
}
dfs(1, -1);
std::cout << f[1][m];
return 0;
}
AcWing 323. 战略游戏【状态机模型+树形dp】
这题的input很恶心
#include <iostream>
#include <cstring>
const int N = 1500 + 10;
int h[N], e[N], ne[N], idx;
int f[N][2];//以i为根的子树,在i节点放或不放哨兵所最少需要的哨兵数量
bool not_root[N];
int n;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
f[u][0] = 0, f[u][1] = 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs(j);
f[u][0] += f[j][1];
f[u][1] += std::min(f[j][1], f[j][0]);
}
}
int main()
{
//这题的输入输出好恶心
while (~(scanf("%d", &n)))
{
//多组数据需要初始化
// memset(f, 0, sizeof f);dfs的时候会初始化的
memset(not_root, false, sizeof not_root);
memset(h, -1, sizeof h), idx = 0;
int a, b, size;
for (int i = 0; i < n; ++ i)
{
scanf("%d:(%d)", &a, &size);
while (size -- )
{
scanf("%d", &b);
add(a, b);
not_root[b] = true;
}
}
int root = 0;
while (not_root[root]) root ++ ;
dfs(root);
std::cout << std::min(f[root][0], f[root][1]) << std::endl;
}
return 0;
}
AcWing 1077. 皇宫看守【状态机+树形dp】
战略游戏是每个边都要有人看守,
本题是每个点都要有人看守
/*
以下注释为早期笔记,希望对你有所帮助
状态机 + 树形Dp问题
状态表示:
f(i, 0):第i号结点被他的父结点安排的守卫看住的方案数
f(i, 1):第i号结点被他的子结点安排的守卫看住的方案数
f(i, 2):第i号结点自己安排守卫看住的方案数
状态计算:(j是i的子结点)
f(i, 0) = sum{min(f(j,1), f(j,2))}
i是被他父结点看住的,那他的子结点要么自己看自己,要么被自己的子结点看住
f(i, 1) = min{w(k) + f(k, 2) - sum{min(f(j,1), f(j,2))}}
i如果是被子结点看住的,那么就要枚举他是被哪个子结点看住的所有方案,对所有方案求最小值
这里的sum不包括j==k的情况,因此需要手动额外减去
f(i, 2) = sum{min(f(j,0), f(j,1), f(j,2))} + w(u)
i是被自己看住的,那他的子结点可以被父结点看住,可以自己看自己,也可以被自己的子结点看住
*/
#include <iostream>
#include <cstring>
const int N = 1500 + 10;
int f[N][3];//以i为根节点,如果是012的情况下,最少的经费是多少
bool not_root[N];
int h[N], w[N], e[N], ne[N], idx;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
int sum = 0;
f[u][0] = 0, f[u][1] = 1e9, f[u][2] = w[u];//f[u][1]是子节点看守的,因此我们要找一个最小值
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
dfs(j);
f[u][0] += std::min(f[j][1], f[j][2]);
f[u][2] += std::min(std::min(f[j][0], f[j][1]), f[j][2]);//自己看自己的话,子节点什么状态都无所谓
sum += std::min(f[j][1], f[j][2]);//没有父节点的看照下,子节点最少需要多少费用
}
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
f[u][1] = std::min(f[u][1], sum - std::min(f[j][1], f[j][2]) + f[j][2]);//要从子树中选一个花费最小的,看看是选那个子节点看照自己
}
}
int main()
{
int n;
std::cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n; ++ i)
{
int id, cost, cnt;
std::cin >> id >> cost >> cnt;
w[id] = cost;
for (int j = 0; j < cnt; ++j)
{
int ver;
std::cin >> ver;
add(id, ver);
not_root[ver] = true;
}
}
//本题节点的下标从1开始,因此root初始值为1
int root = 1;
while (not_root[root]) root ++;
dfs(root);
std::cout << std::min(f[root][1], f[root][2]);//root没有父节点
return 0;
}